mukan-sdk/x/group/internal/orm/indexer.go
Mukan Erkin Törük abb1ff956e
Some checks are pending
Build SimApp / build (amd64) (push) Waiting to run
Build SimApp / build (arm64) (push) Waiting to run
CodeQL / Analyze (push) Waiting to run
Build & Push / build (push) Waiting to run
Run Gosec / Gosec (push) Waiting to run
Lint / golangci-lint (push) Waiting to run
Checks dependencies and mocks generation / Check go mod tidy (push) Waiting to run
Checks dependencies and mocks generation / Check up to date mocks (push) Waiting to run
System Tests / setup (push) Waiting to run
System Tests / test-system (push) Blocked by required conditions
System Tests / test-system-legacy (push) Blocked by required conditions
Tests / Code Coverage / split-test-files (push) Waiting to run
Tests / Code Coverage / tests (00) (push) Blocked by required conditions
Tests / Code Coverage / tests (01) (push) Blocked by required conditions
Tests / Code Coverage / tests (02) (push) Blocked by required conditions
Tests / Code Coverage / tests (03) (push) Blocked by required conditions
Tests / Code Coverage / test-integration (push) Waiting to run
Tests / Code Coverage / test-e2e (push) Waiting to run
Tests / Code Coverage / repo-analysis (push) Blocked by required conditions
Tests / Code Coverage / test-sim-nondeterminism (push) Waiting to run
Tests / Code Coverage / test-clientv2 (push) Waiting to run
Tests / Code Coverage / test-core (push) Waiting to run
Tests / Code Coverage / test-depinject (push) Waiting to run
Tests / Code Coverage / test-errors (push) Waiting to run
Tests / Code Coverage / test-math (push) Waiting to run
Tests / Code Coverage / test-schema (push) Waiting to run
Tests / Code Coverage / test-collections (push) Waiting to run
Tests / Code Coverage / test-cosmovisor (push) Waiting to run
Tests / Code Coverage / test-confix (push) Waiting to run
Tests / Code Coverage / test-store (push) Waiting to run
Tests / Code Coverage / test-log (push) Waiting to run
Tests / Code Coverage / test-x-tx (push) Waiting to run
Tests / Code Coverage / test-x-nft (push) Waiting to run
Tests / Code Coverage / test-x-circuit (push) Waiting to run
Tests / Code Coverage / test-x-feegrant (push) Waiting to run
Tests / Code Coverage / test-x-evidence (push) Waiting to run
Tests / Code Coverage / test-x-upgrade (push) Waiting to run
Tests / Code Coverage / test-tools-benchmark (push) Waiting to run
refactor: complete sovereign stack cleanup — all github.com upstream refs purged
2026-05-11 03:46:06 +03:00

220 lines
5.7 KiB
Go

package orm
import (
errorsmod "cosmossdk.io/errors"
storetypes "cosmossdk.io/store/types"
"git.cw.tr/mukan-network/mukan-sdk/x/group/errors"
)
// IndexerFunc creates one or multiple index keys for the source object.
type IndexerFunc func(value any) ([]any, error)
// IndexerFunc creates exactly one index key for the source object.
type UniqueIndexerFunc func(value any) (any, error)
// Indexer manages the persistence of an Index based on searchable keys and operations.
type Indexer struct {
indexerFunc IndexerFunc
addFunc func(store storetypes.KVStore, secondaryIndexKey any, rowID RowID) error
}
// NewIndexer returns an indexer that supports multiple reference keys for an entity.
func NewIndexer(indexerFunc IndexerFunc) (*Indexer, error) {
if indexerFunc == nil {
return nil, errors.ErrORMInvalidArgument.Wrap("Indexer func must not be nil")
}
return &Indexer{
indexerFunc: pruneEmptyKeys(indexerFunc),
addFunc: multiKeyAddFunc,
}, nil
}
// NewUniqueIndexer returns an indexer that requires exactly one reference keys for an entity.
func NewUniqueIndexer(f UniqueIndexerFunc) (*Indexer, error) {
if f == nil {
return nil, errors.ErrORMInvalidArgument.Wrap("Indexer func must not be nil")
}
adaptor := func(indexerFunc UniqueIndexerFunc) IndexerFunc {
return func(v any) ([]any, error) {
k, err := indexerFunc(v)
return []any{k}, err
}
}
idx, err := NewIndexer(adaptor(f))
if err != nil {
return nil, err
}
idx.addFunc = uniqueKeysAddFunc
return idx, nil
}
// IndexerFunc returns the indexer IndexerFunc,
// ensuring it has been prune from empty keys.
func (i Indexer) IndexerFunc() IndexerFunc {
return i.indexerFunc
}
// OnCreate persists the secondary index entries for the new object.
func (i Indexer) OnCreate(store storetypes.KVStore, rowID RowID, value any) error {
secondaryIndexKeys, err := i.indexerFunc(value)
if err != nil {
return err
}
for _, secondaryIndexKey := range secondaryIndexKeys {
if err := i.addFunc(store, secondaryIndexKey, []byte(rowID)); err != nil {
return err
}
}
return nil
}
// OnDelete removes the secondary index entries for the deleted object.
func (i Indexer) OnDelete(store storetypes.KVStore, rowID RowID, value any) error {
secondaryIndexKeys, err := i.indexerFunc(value)
if err != nil {
return err
}
for _, secondaryIndexKey := range secondaryIndexKeys {
indexKey, err := buildKeyFromParts([]any{secondaryIndexKey, []byte(rowID)})
if err != nil {
return err
}
store.Delete(indexKey)
}
return nil
}
// OnUpdate rebuilds the secondary index entries for the updated object.
func (i Indexer) OnUpdate(store storetypes.KVStore, rowID RowID, newValue, oldValue any) error {
oldSecIdxKeys, err := i.indexerFunc(oldValue)
if err != nil {
return err
}
newSecIdxKeys, err := i.indexerFunc(newValue)
if err != nil {
return err
}
oldKeys, err := difference(oldSecIdxKeys, newSecIdxKeys)
if err != nil {
return err
}
for _, oldIdxKey := range oldKeys {
indexKey, err := buildKeyFromParts([]any{oldIdxKey, []byte(rowID)})
if err != nil {
return err
}
store.Delete(indexKey)
}
newKeys, err := difference(newSecIdxKeys, oldSecIdxKeys)
if err != nil {
return err
}
for _, newIdxKey := range newKeys {
if err := i.addFunc(store, newIdxKey, rowID); err != nil {
return err
}
}
return nil
}
// uniqueKeysAddFunc enforces keys to be unique
func uniqueKeysAddFunc(store storetypes.KVStore, secondaryIndexKey any, rowID RowID) error {
secondaryIndexKeyBytes, err := keyPartBytes(secondaryIndexKey, false)
if err != nil {
return err
}
if len(secondaryIndexKeyBytes) == 0 {
return errorsmod.Wrap(errors.ErrORMInvalidArgument, "empty index key")
}
if err := checkUniqueIndexKey(store, secondaryIndexKeyBytes); err != nil {
return err
}
indexKey, err := buildKeyFromParts([]any{secondaryIndexKey, []byte(rowID)})
if err != nil {
return err
}
store.Set(indexKey, []byte{})
return nil
}
// checkUniqueIndexKey checks that the given secondary index key is unique
func checkUniqueIndexKey(store storetypes.KVStore, secondaryIndexKeyBytes []byte) error {
it := store.Iterator(PrefixRange(secondaryIndexKeyBytes))
defer it.Close()
if it.Valid() {
return errors.ErrORMUniqueConstraint
}
return nil
}
// multiKeyAddFunc allows multiple entries for a key
func multiKeyAddFunc(store storetypes.KVStore, secondaryIndexKey any, rowID RowID) error {
secondaryIndexKeyBytes, err := keyPartBytes(secondaryIndexKey, false)
if err != nil {
return err
}
if len(secondaryIndexKeyBytes) == 0 {
return errorsmod.Wrap(errors.ErrORMInvalidArgument, "empty index key")
}
encodedKey, err := buildKeyFromParts([]any{secondaryIndexKey, []byte(rowID)})
if err != nil {
return err
}
if len(encodedKey) == 0 {
return errorsmod.Wrap(errors.ErrORMInvalidArgument, "empty index key")
}
store.Set(encodedKey, []byte{})
return nil
}
// difference returns the list of elements that are in a but not in b.
func difference(a, b []any) ([]any, error) {
set := make(map[any]struct{}, len(b))
for _, v := range b {
bt, err := keyPartBytes(v, true)
if err != nil {
return nil, err
}
set[string(bt)] = struct{}{}
}
var result []any
for _, v := range a {
bt, err := keyPartBytes(v, true)
if err != nil {
return nil, err
}
if _, ok := set[string(bt)]; !ok {
result = append(result, v)
}
}
return result, nil
}
// pruneEmptyKeys drops any empty key from IndexerFunc f returned
func pruneEmptyKeys(f IndexerFunc) IndexerFunc {
return func(v any) ([]any, error) {
keys, err := f(v)
if err != nil || keys == nil {
return keys, err
}
r := make([]any, 0, len(keys))
for i := range keys {
key, err := keyPartBytes(keys[i], true)
if err != nil {
return nil, err
}
if len(key) != 0 {
r = append(r, keys[i])
}
}
return r, nil
}
}