diff --git a/benchmark_test.go b/benchmark_test.go index dae7167..98cc753 100644 --- a/benchmark_test.go +++ b/benchmark_test.go @@ -17,6 +17,7 @@ import ( "github.com/outofforest/quantum" "github.com/outofforest/quantum/alloc" "github.com/outofforest/quantum/persistent" + "github.com/outofforest/quantum/space" "github.com/outofforest/quantum/tx/genesis" "github.com/outofforest/quantum/tx/transfer" txtypes "github.com/outofforest/quantum/tx/types" @@ -110,6 +111,8 @@ func BenchmarkBalanceTransfer(b *testing.B) { panic(err) } + st := space.NewSpaceTest(s, nil, nil, nil, nil) + hashBuff := s.NewHashBuff() hashMatches := s.NewHashMatches() @@ -126,7 +129,7 @@ func BenchmarkBalanceTransfer(b *testing.B) { panic(err) } - fmt.Println(s.Stats()) + fmt.Println(st.Stats()) fmt.Println("===========================") genesisBalance, genesisExists := s.Query(txtypes.GenesisAccount, hashBuff, hashMatches) @@ -167,7 +170,7 @@ func BenchmarkBalanceTransfer(b *testing.B) { }() func() { - fmt.Println(s.Stats()) + fmt.Println(st.Stats()) genesisBalance, genesisExists := s.Query(txtypes.GenesisAccount, hashBuff, hashMatches) require.True(b, genesisExists) diff --git a/space/collission_test.go b/space/collission_test.go deleted file mode 100644 index e0a602a..0000000 --- a/space/collission_test.go +++ /dev/null @@ -1,57 +0,0 @@ -package space - -import ( - "fmt" - "math" - "sort" - "testing" - - "github.com/stretchr/testify/assert" - - "github.com/outofforest/quantum/types" -) - -var collisions = [][]int{ - {15691551, 62234586, 76498628, 79645586}, - {6417226, 8828927, 78061179, 87384387}, - {9379853, 15271236, 26924827, 39742852}, - {71180670, 73568605, 96077640, 100118418}, - {11317952, 69053141, 82160848, 112455075}, - {33680651, 34881710, 52672514, 56033413}, - {635351, 7564491, 43998577, 77923294}, - {15069177, 60348274, 84185567, 116299206}, - {43622549, 93531002, 108158183, 115087013}, - {32134280, 33645087, 37005304, 83416269}, -} - -// go test -run=TestFindCollisions -v -tags=testing . - -func TestFindCollisions(t *testing.T) { - // Remove SkipNow and use command - // go test -run=TestFindCollisions -v -tags=testing . - // to generate integers with colliding hashes. - t.SkipNow() - - fmt.Println("started") - - m := map[types.KeyHash][]int{} - for i := range math.MaxInt { - h := hashKey(&i, nil, 0) - if h2 := m[h]; len(h2) == 4 { - sort.Ints(h2) - fmt.Printf("%#v\n", h2) - } else { - m[h] = append(m[h], i) - } - } -} - -func TestCollisions(t *testing.T) { - for _, set := range collisions { - m := map[types.KeyHash]struct{}{} - for _, i := range set { - m[hashKey(&i, nil, 0)] = struct{}{} - } - assert.Len(t, m, 1) - } -} diff --git a/space/space.go b/space/space.go index 64378dc..da5a2d0 100644 --- a/space/space.go +++ b/space/space.go @@ -3,7 +3,6 @@ package space import ( "math" "math/bits" - "sort" "unsafe" "github.com/cespare/xxhash" @@ -101,23 +100,62 @@ func (s *Space[K, V]) Find( } } - return s.find(snapshotID, tx, walRecorder, allocator, v, hashBuff, hashMatches) + return s.find(v, snapshotID, tx, walRecorder, allocator, hashBuff, hashMatches, hashKey) } // Query queries the key. func (s *Space[K, V]) Query(key K, hashBuff []byte, hashMatches []uint64) (V, bool) { - volatileAddress := types.Load(&s.config.SpaceRoot.Pointer.VolatileAddress) + keyHash := hashKey(&key, nil, 0) + return s.query(key, keyHash, hashBuff, hashMatches, hashKey) +} + +// Iterator returns iterator iterating over items in space. +func (s *Space[K, V]) Iterator() func(func(item *types.DataItem[K, V]) bool) { + return func(yield func(item *types.DataItem[K, V]) bool) { + s.iterate(s.config.SpaceRoot.Pointer, yield) + } +} + +func (s *Space[K, V]) iterate(pointer *types.Pointer, yield func(item *types.DataItem[K, V]) bool) { + volatileAddress := types.Load(&pointer.VolatileAddress) + switch volatileAddress.State() { + case types.StatePointer: + pointerNode := ProjectPointerNode(s.config.State.Node(volatileAddress.Naked())) + for pi := range pointerNode.Pointers { + p := &pointerNode.Pointers[pi] + if types.Load(&p.VolatileAddress).State() == types.StateFree { + continue + } + + s.iterate(p, yield) + } + case types.StateData: + for _, item := range s.config.DataNodeAssistant.Iterator(s.config.State.Node(volatileAddress)) { + if !yield(item) { + return + } + } + } +} + +func (s *Space[K, V]) query( + key K, + keyHash types.KeyHash, + hashBuff []byte, + hashMatches []uint64, + hashKeyFunc func(key *K, buff []byte, level uint8) types.KeyHash, +) (V, bool) { + volatileAddress := s.config.SpaceRoot.Pointer.VolatileAddress var level uint8 - keyHash := hashKey(&key, nil, level) for volatileAddress.IsSet(types.FlagPointerNode) { if volatileAddress.IsSet(types.FlagHashMod) { - keyHash = hashKey(&key, hashBuff, level) + keyHash = hashKeyFunc(&key, hashBuff, level) } pointerNode := ProjectPointerNode(s.config.State.Node(volatileAddress.Naked())) index := PointerIndex(keyHash, level) - candidateAddress := types.Load(&pointerNode.Pointers[index].VolatileAddress) + candidateAddress := pointerNode.Pointers[index].VolatileAddress switch candidateAddress.State() { case types.StateFree: @@ -149,7 +187,7 @@ func (s *Space[K, V]) Query(key K, hashBuff []byte, hashMatches []uint64) (V, bo } level++ - volatileAddress = types.Load(&pointerNode.Pointers[index].VolatileAddress) + volatileAddress = pointerNode.Pointers[index].VolatileAddress } if volatileAddress == types.FreeAddress { @@ -172,126 +210,6 @@ func (s *Space[K, V]) Query(key K, hashBuff []byte, hashMatches []uint64) (V, bo return s.defaultValue, false } -// Iterator returns iterator iterating over items in space. -func (s *Space[K, V]) Iterator() func(func(item *types.DataItem[K, V]) bool) { - return func(yield func(item *types.DataItem[K, V]) bool) { - s.iterate(s.config.SpaceRoot.Pointer, yield) - } -} - -func (s *Space[K, V]) iterate(pointer *types.Pointer, yield func(item *types.DataItem[K, V]) bool) { - volatileAddress := types.Load(&pointer.VolatileAddress) - switch volatileAddress.State() { - case types.StatePointer: - pointerNode := ProjectPointerNode(s.config.State.Node(volatileAddress.Naked())) - for pi := range pointerNode.Pointers { - p := &pointerNode.Pointers[pi] - if types.Load(&p.VolatileAddress).State() == types.StateFree { - continue - } - - s.iterate(p, yield) - } - case types.StateData: - for _, item := range s.config.DataNodeAssistant.Iterator(s.config.State.Node(volatileAddress)) { - if !yield(item) { - return - } - } - } -} - -// Nodes returns list of nodes used by the space. -func (s *Space[K, V]) Nodes() []types.NodeAddress { - volatileAddress := types.Load(&s.config.SpaceRoot.Pointer.VolatileAddress) - switch volatileAddress.State() { - case types.StateFree: - return nil - case types.StateData: - return []types.NodeAddress{volatileAddress} - } - - nodes := []types.NodeAddress{} - stack := []types.NodeAddress{volatileAddress.Naked()} - - for { - if len(stack) == 0 { - sort.Slice(nodes, func(i, j int) bool { - return nodes[i] < nodes[j] - }) - - return nodes - } - - pointerNodeAddress := stack[len(stack)-1] - stack = stack[:len(stack)-1] - nodes = append(nodes, pointerNodeAddress) - - pointerNode := ProjectPointerNode(s.config.State.Node(pointerNodeAddress.Naked())) - for pi := range pointerNode.Pointers { - volatileAddress := types.Load(&pointerNode.Pointers[pi].VolatileAddress) - switch volatileAddress.State() { - case types.StateFree: - case types.StateData: - nodes = append(nodes, volatileAddress) - case types.StatePointer: - stack = append(stack, volatileAddress.Naked()) - } - } - } -} - -// Stats returns stats about the space. -func (s *Space[K, V]) Stats() (uint64, uint64, uint64, float64) { - volatileAddress := types.Load(&s.config.SpaceRoot.Pointer.VolatileAddress) - switch volatileAddress.State() { - case types.StateFree: - return 0, 0, 0, 0 - case types.StateData: - return 1, 0, 1, 0 - } - - stack := []types.NodeAddress{volatileAddress.Naked()} - - levels := map[types.NodeAddress]uint64{ - volatileAddress.Naked(): 1, - } - var maxLevel, pointerNodes, dataNodes, dataItems uint64 - - for { - if len(stack) == 0 { - return maxLevel, pointerNodes, dataNodes, float64(dataItems) / float64(dataNodes*s.numOfDataItems) - } - - n := stack[len(stack)-1] - level := levels[n] + 1 - pointerNodes++ - stack = stack[:len(stack)-1] - - pointerNode := ProjectPointerNode(s.config.State.Node(n.Naked())) - for pi := range pointerNode.Pointers { - volatileAddress := types.Load(&pointerNode.Pointers[pi].VolatileAddress) - switch volatileAddress.State() { - case types.StateFree: - case types.StateData: - dataNodes++ - if level > maxLevel { - maxLevel = level - } - //nolint:gofmt,revive // looks like a bug in linter - for _, _ = range s.config.DataNodeAssistant.Iterator(s.config.State.Node( - volatileAddress, - )) { - dataItems++ - } - case types.StatePointer: - stack = append(stack, volatileAddress.Naked()) - levels[volatileAddress.Naked()] = level - } - } - } -} - func (s *Space[K, V]) initEntry( v *Entry[K, V], snapshotID types.SnapshotID, @@ -306,7 +224,6 @@ func (s *Space[K, V]) initEntry( copy(initBytes, s.defaultInit) v.keyHash = keyHash v.item.Key = key - v.dataItemIndex = dataItemIndex(v.keyHash, s.numOfDataItems) v.stage = stage if types.Load(&s.config.SpaceRoot.Pointer.VolatileAddress) != types.FreeAddress && @@ -329,36 +246,38 @@ func (s *Space[K, V]) initEntry( return nil } -func (s *Space[K, V]) valueExists( +func (s *Space[K, V]) keyExists( + v *Entry[K, V], snapshotID types.SnapshotID, tx *pipeline.TransactionRequest, walRecorder *wal.Recorder, allocator *alloc.Allocator, - v *Entry[K, V], hashBuff []byte, hashMatches []uint64, + hashKeyFunc func(key *K, buff []byte, level uint8) types.KeyHash, ) (bool, error) { detectUpdate(v) - if err := s.find(snapshotID, tx, walRecorder, allocator, v, hashBuff, hashMatches); err != nil { + if err := s.find(v, snapshotID, tx, walRecorder, allocator, hashBuff, hashMatches, hashKeyFunc); err != nil { return false, err } return v.exists, nil } -func (s *Space[K, V]) readValue( +func (s *Space[K, V]) readKey( + v *Entry[K, V], snapshotID types.SnapshotID, tx *pipeline.TransactionRequest, walRecorder *wal.Recorder, allocator *alloc.Allocator, - v *Entry[K, V], hashBuff []byte, hashMatches []uint64, + hashKeyFunc func(key *K, buff []byte, level uint8) types.KeyHash, ) (V, error) { detectUpdate(v) - if err := s.find(snapshotID, tx, walRecorder, allocator, v, hashBuff, hashMatches); err != nil { + if err := s.find(v, snapshotID, tx, walRecorder, allocator, hashBuff, hashMatches, hashKeyFunc); err != nil { return s.defaultValue, err } @@ -369,18 +288,19 @@ func (s *Space[K, V]) readValue( return v.itemP.Value, nil } -func (s *Space[K, V]) deleteValue( +func (s *Space[K, V]) deleteKey( + v *Entry[K, V], snapshotID types.SnapshotID, tx *pipeline.TransactionRequest, walRecorder *wal.Recorder, allocator *alloc.Allocator, - v *Entry[K, V], hashBuff []byte, hashMatches []uint64, + hashKeyFunc func(key *K, buff []byte, level uint8) types.KeyHash, ) error { detectUpdate(v) - if err := s.find(snapshotID, tx, walRecorder, allocator, v, hashBuff, hashMatches); err != nil { + if err := s.find(v, snapshotID, tx, walRecorder, allocator, hashBuff, hashMatches, hashKeyFunc); err != nil { return err } @@ -403,33 +323,35 @@ func (s *Space[K, V]) deleteValue( return nil } -func (s *Space[K, V]) setValue( +func (s *Space[K, V]) setKey( + v *Entry[K, V], snapshotID types.SnapshotID, tx *pipeline.TransactionRequest, walRecorder *wal.Recorder, allocator *alloc.Allocator, - v *Entry[K, V], value V, hashBuff []byte, hashMatches []uint64, + hashKeyFunc func(key *K, buff []byte, level uint8) types.KeyHash, ) error { v.item.Value = value detectUpdate(v) - return s.set(snapshotID, tx, walRecorder, allocator, v, hashBuff, hashMatches) + return s.set(v, snapshotID, tx, walRecorder, allocator, hashBuff, hashMatches, hashKeyFunc) } func (s *Space[K, V]) find( + v *Entry[K, V], snapshotID types.SnapshotID, tx *pipeline.TransactionRequest, walRecorder *wal.Recorder, allocator *alloc.Allocator, - v *Entry[K, V], hashBuff []byte, hashMatches []uint64, + hashKeyFunc func(key *K, buff []byte, level uint8) types.KeyHash, ) error { - if err := s.walkPointers(v, snapshotID, tx, walRecorder, allocator, hashBuff); err != nil { + if err := s.walkPointers(v, snapshotID, tx, walRecorder, allocator, hashBuff, hashKeyFunc); err != nil { return err } @@ -458,15 +380,16 @@ func (s *Space[K, V]) find( } func (s *Space[K, V]) set( + v *Entry[K, V], snapshotID types.SnapshotID, tx *pipeline.TransactionRequest, walRecorder *wal.Recorder, allocator *alloc.Allocator, - v *Entry[K, V], hashBuff []byte, hashMatches []uint64, + hashKeyFunc func(key *K, buff []byte, level uint8) types.KeyHash, ) error { - if err := s.walkPointers(v, snapshotID, tx, walRecorder, allocator, hashBuff); err != nil { + if err := s.walkPointers(v, snapshotID, tx, walRecorder, allocator, hashBuff, hashKeyFunc); err != nil { return err } @@ -545,7 +468,7 @@ func (s *Space[K, V]) set( v.storeRequest.PointersToStore-- v.level-- - return s.set(snapshotID, tx, walRecorder, allocator, v, hashBuff, hashMatches) + return s.set(v, snapshotID, tx, walRecorder, allocator, hashBuff, hashMatches, hashKeyFunc) } } @@ -554,7 +477,7 @@ func (s *Space[K, V]) set( return err } - return s.set(snapshotID, tx, walRecorder, allocator, v, hashBuff, hashMatches) + return s.set(v, snapshotID, tx, walRecorder, allocator, hashBuff, hashMatches, hashKeyFunc) } func (s *Space[K, V]) splitToIndex(parentNodeAddress types.NodeAddress, index uint64) (uint64, uint64) { @@ -810,9 +733,10 @@ func (s *Space[K, V]) walkPointers( walRecorder *wal.Recorder, allocator *alloc.Allocator, hashBuff []byte, + hashKeyFunc func(key *K, buff []byte, level uint8) types.KeyHash, ) error { for { - more, err := s.walkOnePointer(v, snapshotID, tx, walRecorder, allocator, hashBuff) + more, err := s.walkOnePointer(v, snapshotID, tx, walRecorder, allocator, hashBuff, hashKeyFunc) if err != nil || !more { return err } @@ -826,13 +750,14 @@ func (s *Space[K, V]) walkOnePointer( walRecorder *wal.Recorder, allocator *alloc.Allocator, hashBuff []byte, + hashKeyFunc func(key *K, buff []byte, level uint8) types.KeyHash, ) (bool, error) { volatileAddress := types.Load(&v.storeRequest.Store[v.storeRequest.PointersToStore-1].Pointer.VolatileAddress) if !volatileAddress.IsSet(types.FlagPointerNode) { return false, nil } if volatileAddress.IsSet(types.FlagHashMod) { - v.keyHash = hashKey(&v.item.Key, hashBuff, v.level) + v.keyHash = hashKeyFunc(&v.item.Key, hashBuff, v.level) } pointerNode := ProjectPointerNode(s.config.State.Node(volatileAddress.Naked())) @@ -990,7 +915,7 @@ func (v *Entry[K, V]) Value( hashBuff []byte, hashMatches []uint64, ) (V, error) { - return v.space.readValue(snapshotID, tx, walRecorder, allocator, v, hashBuff, hashMatches) + return v.space.readKey(v, snapshotID, tx, walRecorder, allocator, hashBuff, hashMatches, hashKey[K]) } // Key returns the key from entry. @@ -1007,7 +932,7 @@ func (v *Entry[K, V]) Exists( hashBuff []byte, hashMatches []uint64, ) (bool, error) { - return v.space.valueExists(snapshotID, tx, walRecorder, allocator, v, hashBuff, hashMatches) + return v.space.keyExists(v, snapshotID, tx, walRecorder, allocator, hashBuff, hashMatches, hashKey[K]) } // Set sts value for entry. @@ -1020,7 +945,7 @@ func (v *Entry[K, V]) Set( hashBuff []byte, hashMatches []uint64, ) error { - return v.space.setValue(snapshotID, tx, walRecorder, allocator, v, value, hashBuff, hashMatches) + return v.space.setKey(v, snapshotID, tx, walRecorder, allocator, value, hashBuff, hashMatches, hashKey[K]) } // Delete deletes the entry. @@ -1032,7 +957,7 @@ func (v *Entry[K, V]) Delete( hashBuff []byte, hashMatches []uint64, ) error { - return v.space.deleteValue(snapshotID, tx, walRecorder, allocator, v, hashBuff, hashMatches) + return v.space.deleteKey(v, snapshotID, tx, walRecorder, allocator, hashBuff, hashMatches, hashKey[K]) } func hashKey[K comparable](key *K, buff []byte, level uint8) types.KeyHash { @@ -1050,21 +975,9 @@ func hashKey[K comparable](key *K, buff []byte, level uint8) types.KeyHash { hash = 1 // FIXME (wojciech): Do sth smarter here. } - if types.IsTesting { - hash = testHash(hash) - } - return hash } -func testHash(hash types.KeyHash) types.KeyHash { - return hash & 0x7fffffff -} - -func dataItemIndex(keyHash types.KeyHash, numOfDataItems uint64) uint64 { - return (bits.RotateLeft64(uint64(keyHash), types.UInt64Length/2) ^ uint64(keyHash)) % numOfDataItems -} - func detectUpdate[K, V comparable](v *Entry[K, V]) { switch { case *v.nextDataNode != types.FreeAddress: diff --git a/space/space_test.go b/space/space_test.go deleted file mode 100644 index 851058e..0000000 --- a/space/space_test.go +++ /dev/null @@ -1,69 +0,0 @@ -package space - -import ( - "github.com/outofforest/quantum/alloc" - "github.com/outofforest/quantum/pipeline" - "github.com/outofforest/quantum/types" - "github.com/outofforest/quantum/wal" -) - -func NewSpaceTest[K, V comparable]( - space *Space[K, V], - tx *pipeline.TransactionRequest, - walRecorder *wal.Recorder, - allocator *alloc.Allocator, -) *SpaceTest[K, V] { - return &SpaceTest[K, V]{ - s: space, - tx: tx, - walRecorder: walRecorder, - allocator: allocator, - hashBuff: space.NewHashBuff(), - hashMatches: space.NewHashMatches(), - } -} - -// SpaceTest exposes some private functionality of space to make testing concurrent scenarios possible. -type SpaceTest[K, V comparable] struct { - s *Space[K, V] - tx *pipeline.TransactionRequest - walRecorder *wal.Recorder - allocator *alloc.Allocator - hashBuff []byte - hashMatches []uint64 -} - -func (s *SpaceTest[K, V]) NewEntry( - snapshotID types.SnapshotID, - key K, - keyHash types.KeyHash, - stage uint8, -) (*Entry[K, V], error) { - v := &Entry[K, V]{} - if err := s.s.initEntry(v, snapshotID, s.tx, s.walRecorder, s.allocator, key, keyHash, stage); err != nil { - return nil, err - } - return v, nil -} - -func (s *SpaceTest[K, V]) SplitDataNode(v *Entry[K, V], snapshotID types.SnapshotID) error { - _, err := s.s.splitDataNode(snapshotID, s.tx, s.walRecorder, s.allocator, v.parentIndex, - v.storeRequest.Store[v.storeRequest.PointersToStore-2].Pointer, v.level) - return err -} - -func (s *SpaceTest[K, V]) AddPointerNode(v *Entry[K, V], snapshotID types.SnapshotID, conflict bool) error { - return s.s.addPointerNode(v, snapshotID, s.tx, s.walRecorder, s.allocator, conflict) -} - -func (s *SpaceTest[K, V]) WalkPointers(v *Entry[K, V], snapshotID types.SnapshotID) error { - return s.s.walkPointers(v, snapshotID, s.tx, s.walRecorder, s.allocator, s.hashBuff) -} - -func (s *SpaceTest[K, V]) WalkOnePointer(v *Entry[K, V], snapshotID types.SnapshotID) (bool, error) { - return s.s.walkOnePointer(v, snapshotID, s.tx, s.walRecorder, s.allocator, s.hashBuff) -} - -func (s *SpaceTest[K, V]) WalkDataItems(v *Entry[K, V]) bool { - return s.s.walkDataItems(v, s.hashMatches) -} diff --git a/space/test.go b/space/test.go new file mode 100644 index 0000000..809e913 --- /dev/null +++ b/space/test.go @@ -0,0 +1,201 @@ +package space + +import ( + "sort" + + "github.com/outofforest/quantum/alloc" + "github.com/outofforest/quantum/pipeline" + "github.com/outofforest/quantum/types" + "github.com/outofforest/quantum/wal" +) + +// NewSpaceTest creates new wrapper for space testing. +func NewSpaceTest[K, V comparable]( + s *Space[K, V], + tx *pipeline.TransactionRequest, + walRecorder *wal.Recorder, + allocator *alloc.Allocator, + hashKeyFunc func(key *K, buff []byte, level uint8) types.KeyHash, +) *SpaceTest[K, V] { + return &SpaceTest[K, V]{ + s: s, + tx: tx, + walRecorder: walRecorder, + allocator: allocator, + hashKeyFunc: hashKeyFunc, + hashBuff: s.NewHashBuff(), + hashMatches: s.NewHashMatches(), + } +} + +// SpaceTest exposes some private functionality of space to make testing concurrent scenarios possible. +// +//nolint:revive +type SpaceTest[K, V comparable] struct { + s *Space[K, V] + tx *pipeline.TransactionRequest + walRecorder *wal.Recorder + allocator *alloc.Allocator + hashBuff []byte + hashMatches []uint64 + hashKeyFunc func(key *K, buff []byte, level uint8) types.KeyHash +} + +// NewEntry initializes new entry. +func (s *SpaceTest[K, V]) NewEntry( + snapshotID types.SnapshotID, + key K, + keyHash types.KeyHash, + stage uint8, +) (*Entry[K, V], error) { + v := &Entry[K, V]{} + if err := s.s.initEntry(v, snapshotID, s.tx, s.walRecorder, s.allocator, key, keyHash, stage); err != nil { + return nil, err + } + return v, nil +} + +// KeyExists checks if key is set in the space. +func (s *SpaceTest[K, V]) KeyExists(v *Entry[K, V], snapshotID types.SnapshotID) (bool, error) { + return s.s.keyExists(v, snapshotID, s.tx, s.walRecorder, s.allocator, s.hashBuff, s.hashMatches, s.hashKeyFunc) +} + +// ReadKey reads value for the key. +func (s *SpaceTest[K, V]) ReadKey(v *Entry[K, V], snapshotID types.SnapshotID) (V, error) { + return s.s.readKey(v, snapshotID, s.tx, s.walRecorder, s.allocator, s.hashBuff, s.hashMatches, s.hashKeyFunc) +} + +// DeleteKey deletes key from space. +func (s *SpaceTest[K, V]) DeleteKey(v *Entry[K, V], snapshotID types.SnapshotID) error { + return s.s.deleteKey(v, snapshotID, s.tx, s.walRecorder, s.allocator, s.hashBuff, s.hashMatches, s.hashKeyFunc) +} + +// SetKey sets value for the key. +func (s *SpaceTest[K, V]) SetKey(v *Entry[K, V], snapshotID types.SnapshotID, value V) error { + return s.s.setKey(v, snapshotID, s.tx, s.walRecorder, s.allocator, value, s.hashBuff, s.hashMatches, s.hashKeyFunc) +} + +// SplitDataNode splits data node. +func (s *SpaceTest[K, V]) SplitDataNode(v *Entry[K, V], snapshotID types.SnapshotID) error { + _, err := s.s.splitDataNode(snapshotID, s.tx, s.walRecorder, s.allocator, v.parentIndex, + v.storeRequest.Store[v.storeRequest.PointersToStore-2].Pointer, v.level) + return err +} + +// AddPointerNode adds pointer node. +func (s *SpaceTest[K, V]) AddPointerNode(v *Entry[K, V], snapshotID types.SnapshotID, conflict bool) error { + return s.s.addPointerNode(v, snapshotID, s.tx, s.walRecorder, s.allocator, conflict) +} + +// WalkPointers walk all the pointers to find the key. +func (s *SpaceTest[K, V]) WalkPointers(v *Entry[K, V], snapshotID types.SnapshotID) error { + return s.s.walkPointers(v, snapshotID, s.tx, s.walRecorder, s.allocator, s.hashBuff, s.hashKeyFunc) +} + +// WalkOnePointer walks one pointer only. +func (s *SpaceTest[K, V]) WalkOnePointer(v *Entry[K, V], snapshotID types.SnapshotID) (bool, error) { + return s.s.walkOnePointer(v, snapshotID, s.tx, s.walRecorder, s.allocator, s.hashBuff, s.hashKeyFunc) +} + +// WalkDataItems walks items in data node to find position for the key. +func (s *SpaceTest[K, V]) WalkDataItems(v *Entry[K, V]) bool { + return s.s.walkDataItems(v, s.hashMatches) +} + +// Query queries the space for a key. +func (s *SpaceTest[K, V]) Query(key K, keyHash types.KeyHash) (V, bool) { + return s.s.query(key, keyHash, s.hashBuff, s.hashMatches, s.hashKeyFunc) +} + +// Find finds the location in the tree for key. +func (s *SpaceTest[K, V]) Find(v *Entry[K, V], snapshotID types.SnapshotID) error { + return s.s.find(v, snapshotID, s.tx, s.walRecorder, s.allocator, s.hashBuff, s.hashMatches, s.hashKeyFunc) +} + +// Nodes returns the list of nodes allocated by the tree. +func (s *SpaceTest[K, V]) Nodes() []types.NodeAddress { + switch s.s.config.SpaceRoot.Pointer.VolatileAddress.State() { + case types.StateFree: + return nil + case types.StateData: + return []types.NodeAddress{s.s.config.SpaceRoot.Pointer.VolatileAddress} + } + + nodes := []types.NodeAddress{} + stack := []types.NodeAddress{s.s.config.SpaceRoot.Pointer.VolatileAddress.Naked()} + + for { + if len(stack) == 0 { + sort.Slice(nodes, func(i, j int) bool { + return nodes[i] < nodes[j] + }) + + return nodes + } + + pointerNodeAddress := stack[len(stack)-1] + stack = stack[:len(stack)-1] + nodes = append(nodes, pointerNodeAddress) + + pointerNode := ProjectPointerNode(s.s.config.State.Node(pointerNodeAddress.Naked())) + for pi := range pointerNode.Pointers { + switch pointerNode.Pointers[pi].VolatileAddress.State() { + case types.StateFree: + case types.StateData: + nodes = append(nodes, pointerNode.Pointers[pi].VolatileAddress) + case types.StatePointer: + stack = append(stack, pointerNode.Pointers[pi].VolatileAddress.Naked()) + } + } + } +} + +// Stats returns space-related statistics. +func (s *SpaceTest[K, V]) Stats() (uint64, uint64, uint64, float64) { + switch s.s.config.SpaceRoot.Pointer.VolatileAddress.State() { + case types.StateFree: + return 0, 0, 0, 0 + case types.StateData: + return 1, 0, 1, 0 + } + + stack := []types.NodeAddress{s.s.config.SpaceRoot.Pointer.VolatileAddress.Naked()} + + levels := map[types.NodeAddress]uint64{ + s.s.config.SpaceRoot.Pointer.VolatileAddress.Naked(): 1, + } + var maxLevel, pointerNodes, dataNodes, dataItems uint64 + + for { + if len(stack) == 0 { + return maxLevel, pointerNodes, dataNodes, float64(dataItems) / float64(dataNodes*s.s.numOfDataItems) + } + + n := stack[len(stack)-1] + level := levels[n] + 1 + pointerNodes++ + stack = stack[:len(stack)-1] + + pointerNode := ProjectPointerNode(s.s.config.State.Node(n.Naked())) + for pi := range pointerNode.Pointers { + volatileAddress := types.Load(&pointerNode.Pointers[pi].VolatileAddress) + switch volatileAddress.State() { + case types.StateFree: + case types.StateData: + dataNodes++ + if level > maxLevel { + maxLevel = level + } + //nolint:gofmt,revive // looks like a bug in linter + for _, _ = range s.s.config.DataNodeAssistant.Iterator(s.s.config.State.Node( + volatileAddress, + )) { + dataItems++ + } + case types.StatePointer: + stack = append(stack, volatileAddress.Naked()) + levels[volatileAddress.Naked()] = level + } + } + } +} diff --git a/types/switch.go b/types/switch.go deleted file mode 100644 index f71aa21..0000000 --- a/types/switch.go +++ /dev/null @@ -1,6 +0,0 @@ -//go:build !testing - -package types - -// IsTesting tells if we are inside unit tests. -const IsTesting = false diff --git a/types/switchtest.go b/types/switchtest.go deleted file mode 100644 index 57d3e3e..0000000 --- a/types/switchtest.go +++ /dev/null @@ -1,6 +0,0 @@ -//go:build testing - -package types - -// IsTesting tells if we are inside unit tests. -const IsTesting = true