158 lines
4.7 KiB
Go
158 lines
4.7 KiB
Go
package ormdb
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/binary"
|
|
"math"
|
|
|
|
"google.golang.org/protobuf/reflect/protodesc"
|
|
|
|
"github.com/cosmos/cosmos-sdk/orm/encoding/encodeutil"
|
|
|
|
"google.golang.org/protobuf/proto"
|
|
|
|
"google.golang.org/protobuf/reflect/protoreflect"
|
|
|
|
"github.com/cosmos/cosmos-sdk/orm/encoding/ormkv"
|
|
"github.com/cosmos/cosmos-sdk/orm/model/ormtable"
|
|
"github.com/cosmos/cosmos-sdk/orm/types/ormerrors"
|
|
)
|
|
|
|
// ModuleSchema describes the ORM schema for a module.
|
|
type ModuleSchema struct {
|
|
// FileDescriptors are the file descriptors that contain ORM tables to use in this schema.
|
|
// Each file descriptor must have an unique non-zero uint32 ID associated with it.
|
|
FileDescriptors map[uint32]protoreflect.FileDescriptor
|
|
|
|
// Prefix is an optional prefix to prepend to all keys. It is recommended
|
|
// to leave it empty.
|
|
Prefix []byte
|
|
}
|
|
|
|
// ModuleDB defines the ORM database type to be used by modules.
|
|
type ModuleDB = ormtable.Schema
|
|
|
|
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
|
|
|
|
// GetBackend is the function used to retrieve the table backend.
|
|
// See ormtable.Options.GetBackend for more details.
|
|
GetBackend func(context.Context) (ormtable.Backend, error)
|
|
|
|
// GetReadBackend is the function used to retrieve a table read backend.
|
|
// See ormtable.Options.GetReadBackend for more details.
|
|
GetReadBackend func(context.Context) (ormtable.ReadBackend, error)
|
|
}
|
|
|
|
// NewModuleDB constructs a ModuleDB instance from the provided schema and options.
|
|
func NewModuleDB(schema ModuleSchema, options ModuleDBOptions) (ModuleDB, error) {
|
|
prefix := schema.Prefix
|
|
db := &moduleDB{
|
|
prefix: prefix,
|
|
filesById: map[uint32]*fileDescriptorDB{},
|
|
tablesByName: map[protoreflect.FullName]ormtable.Table{},
|
|
}
|
|
|
|
for id, fileDescriptor := range schema.FileDescriptors {
|
|
if id == 0 {
|
|
return nil, ormerrors.InvalidFileDescriptorID.Wrapf("for %s", fileDescriptor.Path())
|
|
}
|
|
|
|
opts := fileDescriptorDBOptions{
|
|
ID: id,
|
|
Prefix: prefix,
|
|
TypeResolver: options.TypeResolver,
|
|
JSONValidator: options.JSONValidator,
|
|
GetBackend: options.GetBackend,
|
|
GetReadBackend: options.GetReadBackend,
|
|
}
|
|
|
|
if options.FileResolver != nil {
|
|
// if a FileResolver is provided, we use that to resolve the file
|
|
// and not the one provided as a different pinned file descriptor
|
|
// may have been provided
|
|
var err error
|
|
fileDescriptor, err = options.FileResolver.FindFileByPath(fileDescriptor.Path())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
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()]
|
|
}
|