mirror of
https://github.com/nspcc-dev/neo-go.git
synced 2024-11-26 19:42:23 +00:00
d5bea0ad4c
Signed-off-by: Anna Shaleva <shaleva.ann@nspcc.ru>
130 lines
3.8 KiB
Go
130 lines
3.8 KiB
Go
package storage
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
"github.com/nspcc-dev/neo-go/pkg/core/interop"
|
|
"github.com/nspcc-dev/neo-go/pkg/core/storage"
|
|
"github.com/nspcc-dev/neo-go/pkg/util/slice"
|
|
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
|
)
|
|
|
|
// Storage iterator options.
|
|
const (
|
|
FindDefault = 0
|
|
FindKeysOnly = 1 << 0
|
|
FindRemovePrefix = 1 << 1
|
|
FindValuesOnly = 1 << 2
|
|
FindDeserialize = 1 << 3
|
|
FindPick0 = 1 << 4
|
|
FindPick1 = 1 << 5
|
|
FindBackwards = 1 << 7
|
|
|
|
FindAll = FindDefault | FindKeysOnly | FindRemovePrefix | FindValuesOnly |
|
|
FindDeserialize | FindPick0 | FindPick1 | FindBackwards
|
|
)
|
|
|
|
// Iterator is an iterator state representation.
|
|
type Iterator struct {
|
|
seekCh chan storage.KeyValue
|
|
curr storage.KeyValue
|
|
next bool
|
|
opts int64
|
|
prefix []byte
|
|
}
|
|
|
|
// NewIterator creates a new Iterator with the given options for the given channel of store.Seek results.
|
|
func NewIterator(seekCh chan storage.KeyValue, prefix []byte, opts int64) *Iterator {
|
|
return &Iterator{
|
|
seekCh: seekCh,
|
|
opts: opts,
|
|
prefix: slice.Copy(prefix),
|
|
}
|
|
}
|
|
|
|
// Next advances the iterator and returns true if Value can be called at the
|
|
// current position.
|
|
func (s *Iterator) Next() bool {
|
|
s.curr, s.next = <-s.seekCh
|
|
return s.next
|
|
}
|
|
|
|
// Value returns current iterators value (exact type depends on options this
|
|
// iterator was created with).
|
|
func (s *Iterator) Value() stackitem.Item {
|
|
if !s.next {
|
|
panic("iterator index out of range")
|
|
}
|
|
key := s.curr.Key
|
|
if s.opts&FindRemovePrefix == 0 {
|
|
key = append(s.prefix, key...)
|
|
}
|
|
if s.opts&FindKeysOnly != 0 {
|
|
return stackitem.NewByteArray(key)
|
|
}
|
|
value := stackitem.Item(stackitem.NewByteArray(s.curr.Value))
|
|
if s.opts&FindDeserialize != 0 {
|
|
bs := s.curr.Value
|
|
var err error
|
|
value, err = stackitem.Deserialize(bs)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
if s.opts&FindPick0 != 0 {
|
|
value = value.Value().([]stackitem.Item)[0]
|
|
} else if s.opts&FindPick1 != 0 {
|
|
value = value.Value().([]stackitem.Item)[1]
|
|
}
|
|
if s.opts&FindValuesOnly != 0 {
|
|
return value
|
|
}
|
|
return stackitem.NewStruct([]stackitem.Item{
|
|
stackitem.NewByteArray(key),
|
|
value,
|
|
})
|
|
}
|
|
|
|
// Find finds stored key-value pair.
|
|
func Find(ic *interop.Context) error {
|
|
stcInterface := ic.VM.Estack().Pop().Value()
|
|
stc, ok := stcInterface.(*Context)
|
|
if !ok {
|
|
return fmt.Errorf("%T is not a storage,Context", stcInterface)
|
|
}
|
|
prefix := ic.VM.Estack().Pop().Bytes()
|
|
opts := ic.VM.Estack().Pop().BigInt().Int64()
|
|
if opts&^FindAll != 0 {
|
|
return fmt.Errorf("%w: unknown flag", errFindInvalidOptions)
|
|
}
|
|
if opts&FindKeysOnly != 0 &&
|
|
opts&(FindDeserialize|FindPick0|FindPick1) != 0 {
|
|
return fmt.Errorf("%w KeysOnly conflicts with other options", errFindInvalidOptions)
|
|
}
|
|
if opts&FindValuesOnly != 0 &&
|
|
opts&(FindKeysOnly|FindRemovePrefix) != 0 {
|
|
return fmt.Errorf("%w: KeysOnly conflicts with ValuesOnly", errFindInvalidOptions)
|
|
}
|
|
if opts&FindPick0 != 0 && opts&FindPick1 != 0 {
|
|
return fmt.Errorf("%w: Pick0 conflicts with Pick1", errFindInvalidOptions)
|
|
}
|
|
if opts&FindDeserialize == 0 && (opts&FindPick0 != 0 || opts&FindPick1 != 0) {
|
|
return fmt.Errorf("%w: PickN is specified without Deserialize", errFindInvalidOptions)
|
|
}
|
|
bkwrds := opts&FindBackwards != 0
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
seekres := ic.DAO.SeekAsync(ctx, stc.ID, storage.SeekRange{Prefix: prefix, Backwards: bkwrds})
|
|
item := NewIterator(seekres, prefix, opts)
|
|
ic.VM.Estack().PushItem(stackitem.NewInterop(item))
|
|
ic.RegisterCancelFunc(func() {
|
|
cancel()
|
|
// Underlying persistent store is likely to be a private MemCachedStore. Thus,
|
|
// to avoid concurrent map iteration and map write we need to wait until internal
|
|
// seek goroutine is finished, because it can access underlying persistent store.
|
|
for range seekres { //nolint:revive //empty-block
|
|
}
|
|
})
|
|
|
|
return nil
|
|
}
|