221 lines
6.4 KiB
Go
221 lines
6.4 KiB
Go
package ormdb
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/binary"
|
|
"errors"
|
|
"fmt"
|
|
"math"
|
|
|
|
"google.golang.org/protobuf/proto"
|
|
"google.golang.org/protobuf/reflect/protodesc"
|
|
"google.golang.org/protobuf/reflect/protoreflect"
|
|
"google.golang.org/protobuf/reflect/protoregistry"
|
|
|
|
ormv1alpha1 "cosmossdk.io/api/cosmos/orm/v1alpha1"
|
|
"cosmossdk.io/core/appmodule"
|
|
"cosmossdk.io/core/store"
|
|
"cosmossdk.io/orm/encoding/encodeutil"
|
|
"cosmossdk.io/orm/encoding/ormkv"
|
|
"cosmossdk.io/orm/model/ormtable"
|
|
"cosmossdk.io/orm/types/ormerrors"
|
|
)
|
|
|
|
// ModuleDB defines the ORM database type to be used by modules.
|
|
type ModuleDB interface {
|
|
ormtable.Schema
|
|
|
|
// GenesisHandler returns an implementation of appmodule.HasGenesis
|
|
// to be embedded in or called from app module implementations.
|
|
// Ex:
|
|
// type AppModule struct {
|
|
// appmodule.HasGenesis
|
|
// }
|
|
//
|
|
// func NewKeeper(db ModuleDB) *Keeper {
|
|
// return &Keeper{genesisHandler: db.GenesisHandler()}
|
|
// }
|
|
//
|
|
// func NewAppModule(keeper keeper.Keeper) AppModule {
|
|
// return AppModule{HasGenesis: keeper.GenesisHandler()}
|
|
// }
|
|
GenesisHandler() appmodule.HasGenesisAuto
|
|
|
|
private()
|
|
}
|
|
|
|
type moduleDB struct {
|
|
prefix []byte
|
|
filesByID map[uint32]*fileDescriptorDB
|
|
tablesByName map[protoreflect.FullName]ormtable.Table
|
|
}
|
|
|
|
// ModuleDBOptions are options for constructing a ModuleDB.
|
|
type ModuleDBOptions struct {
|
|
// TypeResolver is an optional type resolver to be used when unmarshaling
|
|
// protobuf messages. If it is nil, protoregistry.GlobalTypes will be used.
|
|
TypeResolver ormtable.TypeResolver
|
|
|
|
// FileResolver is an optional file resolver that can be used to retrieve
|
|
// pinned file descriptors that may be different from those available at
|
|
// runtime. The file descriptor versions returned by this resolver will be
|
|
// used instead of the ones provided at runtime by the ModuleSchema.
|
|
FileResolver protodesc.Resolver
|
|
|
|
// JSONValidator is an optional validator that can be used for validating
|
|
// messaging when using ValidateJSON. If it is nil, DefaultJSONValidator
|
|
// will be used
|
|
JSONValidator func(proto.Message) error
|
|
|
|
// KVStoreService is the storage service to use for the DB if default KV-store storage is used.
|
|
KVStoreService store.KVStoreService
|
|
|
|
// KVStoreService is the storage service to use for the DB if memory storage is used.
|
|
MemoryStoreService store.MemoryStoreService
|
|
|
|
// KVStoreService is the storage service to use for the DB if transient storage is used.
|
|
TransientStoreService store.TransientStoreService
|
|
}
|
|
|
|
// NewModuleDB constructs a ModuleDB instance from the provided schema and options.
|
|
func NewModuleDB(schema *ormv1alpha1.ModuleSchemaDescriptor, options ModuleDBOptions) (ModuleDB, error) {
|
|
prefix := schema.Prefix
|
|
db := &moduleDB{
|
|
prefix: prefix,
|
|
filesByID: map[uint32]*fileDescriptorDB{},
|
|
tablesByName: map[protoreflect.FullName]ormtable.Table{},
|
|
}
|
|
|
|
fileResolver := options.FileResolver
|
|
if fileResolver == nil {
|
|
fileResolver = protoregistry.GlobalFiles
|
|
}
|
|
|
|
for _, entry := range schema.SchemaFile {
|
|
var backendResolver ormtable.BackendResolver
|
|
|
|
switch entry.StorageType {
|
|
case ormv1alpha1.StorageType_STORAGE_TYPE_DEFAULT_UNSPECIFIED:
|
|
service := options.KVStoreService
|
|
if service != nil {
|
|
// for testing purposes, the ORM allows KVStoreService to be omitted
|
|
// and a default test backend can be used
|
|
backendResolver = func(ctx context.Context) (ormtable.ReadBackend, error) {
|
|
kvStore := service.OpenKVStore(ctx)
|
|
return ormtable.NewBackend(ormtable.BackendOptions{
|
|
CommitmentStore: kvStore,
|
|
IndexStore: kvStore,
|
|
}), nil
|
|
}
|
|
}
|
|
case ormv1alpha1.StorageType_STORAGE_TYPE_MEMORY:
|
|
service := options.MemoryStoreService
|
|
if service == nil {
|
|
return nil, errors.New("missing MemoryStoreService")
|
|
}
|
|
|
|
backendResolver = func(ctx context.Context) (ormtable.ReadBackend, error) {
|
|
kvStore := service.OpenMemoryStore(ctx)
|
|
return ormtable.NewBackend(ormtable.BackendOptions{
|
|
CommitmentStore: kvStore,
|
|
IndexStore: kvStore,
|
|
}), nil
|
|
}
|
|
case ormv1alpha1.StorageType_STORAGE_TYPE_TRANSIENT:
|
|
service := options.TransientStoreService
|
|
if service == nil {
|
|
return nil, errors.New("missing TransientStoreService")
|
|
}
|
|
|
|
backendResolver = func(ctx context.Context) (ormtable.ReadBackend, error) {
|
|
kvStore := service.OpenTransientStore(ctx)
|
|
return ormtable.NewBackend(ormtable.BackendOptions{
|
|
CommitmentStore: kvStore,
|
|
IndexStore: kvStore,
|
|
}), nil
|
|
}
|
|
default:
|
|
return nil, fmt.Errorf("unsupported storage type %s", entry.StorageType)
|
|
}
|
|
|
|
id := entry.Id
|
|
fileDescriptor, err := fileResolver.FindFileByPath(entry.ProtoFileName)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if id == 0 {
|
|
return nil, ormerrors.InvalidFileDescriptorID.Wrapf("for %s", fileDescriptor.Path())
|
|
}
|
|
|
|
opts := fileDescriptorDBOptions{
|
|
ID: id,
|
|
Prefix: prefix,
|
|
TypeResolver: options.TypeResolver,
|
|
JSONValidator: options.JSONValidator,
|
|
BackendResolver: backendResolver,
|
|
}
|
|
|
|
fdSchema, err := newFileDescriptorDB(fileDescriptor, opts)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
db.filesByID[id] = fdSchema
|
|
for name, table := range fdSchema.tablesByName {
|
|
if _, ok := db.tablesByName[name]; ok {
|
|
return nil, ormerrors.UnexpectedError.Wrapf("duplicate table %s", name)
|
|
}
|
|
|
|
db.tablesByName[name] = table
|
|
}
|
|
}
|
|
|
|
return db, nil
|
|
}
|
|
|
|
func (m moduleDB) DecodeEntry(k, v []byte) (ormkv.Entry, error) {
|
|
r := bytes.NewReader(k)
|
|
err := encodeutil.SkipPrefix(r, m.prefix)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
id, err := binary.ReadUvarint(r)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if id > math.MaxUint32 {
|
|
return nil, ormerrors.UnexpectedDecodePrefix.Wrapf("uint32 varint id out of range %d", id)
|
|
}
|
|
|
|
fileSchema, ok := m.filesByID[uint32(id)]
|
|
if !ok {
|
|
return nil, ormerrors.UnexpectedDecodePrefix.Wrapf("can't find FileDescriptor schema with id %d", id)
|
|
}
|
|
|
|
return fileSchema.DecodeEntry(k, v)
|
|
}
|
|
|
|
func (m moduleDB) EncodeEntry(entry ormkv.Entry) (k, v []byte, err error) {
|
|
tableName := entry.GetTableName()
|
|
table, ok := m.tablesByName[tableName]
|
|
if !ok {
|
|
return nil, nil, ormerrors.BadDecodeEntry.Wrapf("can't find table %s", tableName)
|
|
}
|
|
|
|
return table.EncodeEntry(entry)
|
|
}
|
|
|
|
func (m moduleDB) GetTable(message proto.Message) ormtable.Table {
|
|
return m.tablesByName[message.ProtoReflect().Descriptor().FullName()]
|
|
}
|
|
|
|
func (m moduleDB) GenesisHandler() appmodule.HasGenesisAuto {
|
|
return appModuleGenesisWrapper{m}
|
|
}
|
|
|
|
func (moduleDB) private() {}
|