cosmos-sdk/x/group/internal/orm/table.go

305 lines
9.8 KiB
Go

package orm
import (
"bytes"
"reflect"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/store/prefix"
"github.com/cosmos/cosmos-sdk/store/types"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/cosmos/cosmos-sdk/x/group/errors"
)
var (
_ Indexable = &table{}
_ TableExportable = &table{}
)
// table is the high level object to storage mapper functionality. Persistent
// entities are stored by an unique identifier called `RowID`. The table struct
// does not:
// - enforce uniqueness of the `RowID`
// - enforce prefix uniqueness of keys, i.e. not allowing one key to be a prefix
// of another
// - optimize Gas usage conditions
// The caller must ensure that these things are handled. The table struct is
// private, so that we only have custom tables built on top of table, that do satisfy
// these requirements.
type table struct {
model reflect.Type
prefix [2]byte
afterSet []AfterSetInterceptor
afterDelete []AfterDeleteInterceptor
cdc codec.Codec
}
// newTable creates a new table
func newTable(prefix [2]byte, model codec.ProtoMarshaler, cdc codec.Codec) (*table, error) {
if model == nil {
return nil, errors.ErrORMInvalidArgument.Wrap("Model must not be nil")
}
tp := reflect.TypeOf(model)
if tp.Kind() == reflect.Ptr {
tp = tp.Elem()
}
return &table{
prefix: prefix,
model: tp,
cdc: cdc,
}, nil
}
// RowGetter returns a type safe RowGetter.
func (a table) RowGetter() RowGetter {
return NewTypeSafeRowGetter(a.prefix, a.model, a.cdc)
}
// AddAfterSetInterceptor can be used to register a callback function that is executed after an object is created and/or updated.
func (a *table) AddAfterSetInterceptor(interceptor AfterSetInterceptor) {
a.afterSet = append(a.afterSet, interceptor)
}
// AddAfterDeleteInterceptor can be used to register a callback function that is executed after an object is deleted.
func (a *table) AddAfterDeleteInterceptor(interceptor AfterDeleteInterceptor) {
a.afterDelete = append(a.afterDelete, interceptor)
}
// Create persists the given object under the rowID key, returning an
// errors.ErrORMUniqueConstraint if a value already exists at that key.
//
// Create iterates through the registered callbacks that may add secondary index
// keys.
func (a table) Create(store sdk.KVStore, rowID RowID, obj codec.ProtoMarshaler) error {
if a.Has(store, rowID) {
return errors.ErrORMUniqueConstraint
}
return a.Set(store, rowID, obj)
}
// Update updates the given object under the rowID key. It expects the key to
// exists already and fails with an `sdkerrors.ErrNotFound` otherwise. Any caller must
// therefore make sure that this contract is fulfilled. Parameters must not be
// nil.
//
// Update triggers all "after set" hooks that may add or remove secondary index keys.
func (a table) Update(store sdk.KVStore, rowID RowID, newValue codec.ProtoMarshaler) error {
if !a.Has(store, rowID) {
return sdkerrors.ErrNotFound
}
return a.Set(store, rowID, newValue)
}
// Set persists the given object under the rowID key. It does not check if the
// key already exists and overwrites the value if it does.
//
// Set iterates through the registered callbacks that may add secondary index
// keys.
func (a table) Set(store sdk.KVStore, rowID RowID, newValue codec.ProtoMarshaler) error {
if len(rowID) == 0 {
return errors.ErrORMEmptyKey
}
if err := assertCorrectType(a.model, newValue); err != nil {
return err
}
if err := assertValid(newValue); err != nil {
return err
}
pStore := prefix.NewStore(store, a.prefix[:])
var oldValue codec.ProtoMarshaler
if a.Has(store, rowID) {
oldValue = reflect.New(a.model).Interface().(codec.ProtoMarshaler)
a.GetOne(store, rowID, oldValue)
}
newValueEncoded, err := a.cdc.Marshal(newValue)
if err != nil {
return sdkerrors.Wrapf(err, "failed to serialize %T", newValue)
}
pStore.Set(rowID, newValueEncoded)
for i, itc := range a.afterSet {
if err := itc(store, rowID, newValue, oldValue); err != nil {
return sdkerrors.Wrapf(err, "interceptor %d failed", i)
}
}
return nil
}
func assertValid(obj codec.ProtoMarshaler) error {
if v, ok := obj.(Validateable); ok {
if err := v.ValidateBasic(); err != nil {
return err
}
}
return nil
}
// Delete removes the object under the rowID key. It expects the key to exists
// already and fails with a `sdkerrors.ErrNotFound` otherwise. Any caller must therefore
// make sure that this contract is fulfilled.
//
// Delete iterates through the registered callbacks that remove secondary index
// keys.
func (a table) Delete(store sdk.KVStore, rowID RowID) error {
pStore := prefix.NewStore(store, a.prefix[:])
var oldValue = reflect.New(a.model).Interface().(codec.ProtoMarshaler)
if err := a.GetOne(store, rowID, oldValue); err != nil {
return sdkerrors.Wrap(err, "load old value")
}
pStore.Delete(rowID)
for i, itc := range a.afterDelete {
if err := itc(store, rowID, oldValue); err != nil {
return sdkerrors.Wrapf(err, "delete interceptor %d failed", i)
}
}
return nil
}
// Has checks if a key exists. Returns false when the key is empty or nil
// because we don't allow creation of values without a key.
func (a table) Has(store sdk.KVStore, key RowID) bool {
if len(key) == 0 {
return false
}
pStore := prefix.NewStore(store, a.prefix[:])
it := pStore.Iterator(PrefixRange(key))
defer it.Close()
return it.Valid()
}
// GetOne load the object persisted for the given RowID into the dest parameter.
// If none exists or `rowID==nil` then `sdkerrors.ErrNotFound` is returned instead.
// Parameters must not be nil - we don't allow creation of values with empty keys.
func (a table) GetOne(store sdk.KVStore, rowID RowID, dest codec.ProtoMarshaler) error {
if len(rowID) == 0 {
return sdkerrors.ErrNotFound
}
x := NewTypeSafeRowGetter(a.prefix, a.model, a.cdc)
return x(store, rowID, dest)
}
// PrefixScan returns an Iterator over a domain of keys in ascending order. End is exclusive.
// Start is an MultiKeyIndex key or prefix. It must be less than end, or the Iterator is invalid.
// Iterator must be closed by caller.
// To iterate over entire domain, use PrefixScan(nil, nil)
//
// WARNING: The use of a PrefixScan can be very expensive in terms of Gas. Please make sure you do not expose
// this as an endpoint to the public without further limits.
// Example:
// it, err := idx.PrefixScan(ctx, start, end)
// if err !=nil {
// return err
// }
// const defaultLimit = 20
// it = LimitIterator(it, defaultLimit)
//
// CONTRACT: No writes may happen within a domain while an iterator exists over it.
func (a table) PrefixScan(store sdk.KVStore, start, end RowID) (Iterator, error) {
if start != nil && end != nil && bytes.Compare(start, end) >= 0 {
return NewInvalidIterator(), sdkerrors.Wrap(errors.ErrORMInvalidArgument, "start must be before end")
}
pStore := prefix.NewStore(store, a.prefix[:])
return &typeSafeIterator{
store: store,
rowGetter: NewTypeSafeRowGetter(a.prefix, a.model, a.cdc),
it: pStore.Iterator(start, end),
}, nil
}
// ReversePrefixScan returns an Iterator over a domain of keys in descending order. End is exclusive.
// Start is an MultiKeyIndex key or prefix. It must be less than end, or the Iterator is invalid and error is returned.
// Iterator must be closed by caller.
// To iterate over entire domain, use PrefixScan(nil, nil)
//
// WARNING: The use of a ReversePrefixScan can be very expensive in terms of Gas. Please make sure you do not expose
// this as an endpoint to the public without further limits. See `LimitIterator`
//
// CONTRACT: No writes may happen within a domain while an iterator exists over it.
func (a table) ReversePrefixScan(store sdk.KVStore, start, end RowID) (Iterator, error) {
if start != nil && end != nil && bytes.Compare(start, end) >= 0 {
return NewInvalidIterator(), sdkerrors.Wrap(errors.ErrORMInvalidArgument, "start must be before end")
}
pStore := prefix.NewStore(store, a.prefix[:])
return &typeSafeIterator{
store: store,
rowGetter: NewTypeSafeRowGetter(a.prefix, a.model, a.cdc),
it: pStore.ReverseIterator(start, end),
}, nil
}
// Export stores all the values in the table in the passed ModelSlicePtr.
func (a table) Export(store sdk.KVStore, dest ModelSlicePtr) (uint64, error) {
it, err := a.PrefixScan(store, nil, nil)
if err != nil {
return 0, sdkerrors.Wrap(err, "table Export failure when exporting table data")
}
_, err = ReadAll(it, dest)
if err != nil {
return 0, err
}
return 0, nil
}
// Import clears the table and initializes it from the given data interface{}.
// data should be a slice of structs that implement PrimaryKeyed.
func (a table) Import(store sdk.KVStore, data interface{}, _ uint64) error {
// Clear all data
pStore := prefix.NewStore(store, a.prefix[:])
it := pStore.Iterator(nil, nil)
defer it.Close()
for ; it.Valid(); it.Next() {
if err := a.Delete(store, it.Key()); err != nil {
return err
}
}
// Provided data must be a slice
modelSlice := reflect.ValueOf(data)
if modelSlice.Kind() != reflect.Slice {
return sdkerrors.Wrap(errors.ErrORMInvalidArgument, "data must be a slice")
}
// Import values from slice
for i := 0; i < modelSlice.Len(); i++ {
obj, ok := modelSlice.Index(i).Interface().(PrimaryKeyed)
if !ok {
return sdkerrors.Wrapf(errors.ErrORMInvalidArgument, "unsupported type :%s", reflect.TypeOf(data).Elem().Elem())
}
err := a.Create(store, PrimaryKey(obj), obj)
if err != nil {
return err
}
}
return nil
}
// typeSafeIterator is initialized with a type safe RowGetter only.
type typeSafeIterator struct {
store sdk.KVStore
rowGetter RowGetter
it types.Iterator
}
func (i typeSafeIterator) LoadNext(dest codec.ProtoMarshaler) (RowID, error) {
if !i.it.Valid() {
return nil, errors.ErrORMIteratorDone
}
rowID := i.it.Key()
i.it.Next()
return rowID, i.rowGetter(i.store, rowID, dest)
}
func (i typeSafeIterator) Close() error {
i.it.Close()
return nil
}