package ormtable import ( "context" "fmt" "github.com/cosmos/cosmos-sdk/orm/types/kv" ) // ReadBackend defines the type used for read-only ORM operations. type ReadBackend interface { // CommitmentStoreReader returns the reader for the commitment store. CommitmentStoreReader() kv.ReadonlyStore // IndexStoreReader returns the reader for the index store. IndexStoreReader() kv.ReadonlyStore private() } // Backend defines the type used for read-write ORM operations. // Unlike ReadBackend, write access to the underlying kv-store // is hidden so that this can be fully encapsulated by the ORM. type Backend interface { ReadBackend // CommitmentStore returns the merklized commitment store. CommitmentStore() kv.Store // IndexStore returns the index store if a separate one exists, // otherwise it the commitment store. IndexStore() kv.Store // ValidateHooks returns a ValidateHooks instance or nil. ValidateHooks() ValidateHooks // WithValidateHooks returns a copy of this backend with the provided validate hooks. WithValidateHooks(ValidateHooks) Backend // WriteHooks returns a WriteHooks instance of nil. WriteHooks() WriteHooks // WithWriteHooks returns a copy of this backend with the provided write hooks. WithWriteHooks(WriteHooks) Backend } // ReadBackendOptions defines options for creating a ReadBackend. // Read context can optionally define two stores - a commitment store // that is backed by a merkle tree and an index store that isn't. // If the index store is not defined, the commitment store will be // used for all operations. type ReadBackendOptions struct { // CommitmentStoreReader is a reader for the commitment store. CommitmentStoreReader kv.ReadonlyStore // IndexStoreReader is an optional reader for the index store. // If it is nil the CommitmentStoreReader will be used. IndexStoreReader kv.ReadonlyStore } type readBackend struct { commitmentReader kv.ReadonlyStore indexReader kv.ReadonlyStore } func (r readBackend) CommitmentStoreReader() kv.ReadonlyStore { return r.commitmentReader } func (r readBackend) IndexStoreReader() kv.ReadonlyStore { return r.indexReader } func (readBackend) private() {} // NewReadBackend creates a new ReadBackend. func NewReadBackend(options ReadBackendOptions) ReadBackend { indexReader := options.IndexStoreReader if indexReader == nil { indexReader = options.CommitmentStoreReader } return &readBackend{ commitmentReader: options.CommitmentStoreReader, indexReader: indexReader, } } type backend struct { commitmentStore kv.Store indexStore kv.Store validateHooks ValidateHooks writeHooks WriteHooks } func (c backend) ValidateHooks() ValidateHooks { return c.validateHooks } func (c backend) WithValidateHooks(hooks ValidateHooks) Backend { c.validateHooks = hooks return c } func (c backend) WriteHooks() WriteHooks { return c.writeHooks } func (c backend) WithWriteHooks(hooks WriteHooks) Backend { c.writeHooks = hooks return c } func (backend) private() {} func (c backend) CommitmentStoreReader() kv.ReadonlyStore { return c.commitmentStore } func (c backend) IndexStoreReader() kv.ReadonlyStore { return c.indexStore } func (c backend) CommitmentStore() kv.Store { return c.commitmentStore } func (c backend) IndexStore() kv.Store { return c.indexStore } // BackendOptions defines options for creating a Backend. // Context can optionally define two stores - a commitment store // that is backed by a merkle tree and an index store that isn't. // If the index store is not defined, the commitment store will be // used for all operations. type BackendOptions struct { // CommitmentStore is the commitment store. CommitmentStore kv.Store // IndexStore is the optional index store. // If it is nil the CommitmentStore will be used. IndexStore kv.Store // ValidateHooks are optional hooks into ORM insert, update and delete operations. ValidateHooks ValidateHooks WriteHooks WriteHooks } // NewBackend creates a new Backend. func NewBackend(options BackendOptions) Backend { indexStore := options.IndexStore if indexStore == nil { indexStore = options.CommitmentStore } return &backend{ commitmentStore: options.CommitmentStore, indexStore: indexStore, validateHooks: options.ValidateHooks, writeHooks: options.WriteHooks, } } // BackendResolver resolves a backend from the context or returns an error. // Callers should type cast the returned ReadBackend to Backend to test whether // the backend is writable. type BackendResolver func(context.Context) (ReadBackend, error) // WrapContextDefault performs the default wrapping of a backend in a context. // This should be used primarily for testing purposes and production code // should use some other framework specific wrapping (for instance using // "store keys"). func WrapContextDefault(backend ReadBackend) context.Context { return context.WithValue(context.Background(), defaultContextKey, backend) } type contextKeyType string var defaultContextKey = contextKeyType("backend") func getBackendDefault(ctx context.Context) (ReadBackend, error) { value := ctx.Value(defaultContextKey) if value == nil { return nil, fmt.Errorf("can't resolve backend") } backend, ok := value.(ReadBackend) if !ok { return nil, fmt.Errorf("expected value of type %T, instead got %T", backend, value) } return backend, nil }