Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Index selectors #96

Merged
merged 45 commits into from
Apr 11, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
3771e81
Query.Intersect API & test
marino39 Mar 20, 2023
fa6c2ef
Naive implementation of Query.Intersects
marino39 Mar 20, 2023
a220743
Improve Query.Intersect benchmark
marino39 Mar 20, 2023
8968be7
Query.Intersect add sort / filter / offset / limit
marino39 Mar 20, 2023
c14e8ea
Index.Iter implementation
marino39 Mar 21, 2023
4dac79b
Use Index.Iter in Table Scan functions
marino39 Mar 21, 2023
490f432
Extract index update functions from Table to Index
marino39 Mar 21, 2023
e5db7e4
Extract index update functions from Table to Index: p2
marino39 Mar 21, 2023
13e42f5
KeySuccessor args change
marino39 Mar 21, 2023
a4bd17a
Fix: allocations is Table.PrimaryKey
marino39 Mar 21, 2023
ac937b9
Optimize Index.OnUpdate, so it requires less writes to database
marino39 Mar 22, 2023
3a4ee78
Use new Index callbacks in Table.reindex func
marino39 Mar 22, 2023
7a50e94
Add Intersect function to Index and use it in Query
marino39 Mar 22, 2023
80b2d1d
Benchmark Query.Intersect
marino39 Mar 23, 2023
e16bdc8
Move Index.Intersects to be map based because of poor performance
marino39 Mar 23, 2023
d8aa922
Reduce number of allocations in Index.Intersect
marino39 Mar 23, 2023
11e01d1
Add Query.Intersect().Order() test
marino39 Mar 23, 2023
fd5ce51
Query.Intersect().After() implementation
marino39 Mar 23, 2023
365ad1c
Query.After now works with Query.Order and Query.Intersect
marino39 Mar 24, 2023
323c907
Make sure that intersect queries do not have any modifiers like limit…
marino39 Mar 27, 2023
d39c691
Index selectors API proposition
marino39 Mar 27, 2023
d98773b
Merge branch 'master' into new_index_selectors
marino39 Mar 29, 2023
6f43140
Selector struct impl
marino39 Mar 29, 2023
8aa787f
Selector interface based approach
marino39 Mar 30, 2023
2790aa8
Table / Index.Intersect ported to use Selector
marino39 Mar 30, 2023
6afb4a8
Add table utils unit test
marino39 Mar 30, 2023
58fe15e
Use Selector in Query
marino39 Mar 30, 2023
863df97
Add files to .gitignore
marino39 Mar 30, 2023
cda655e
Fix: Example app
marino39 Mar 30, 2023
80b0f91
Cleanup
marino39 Mar 30, 2023
f63efaf
Table.Get SelectorRange implementation
marino39 Mar 31, 2023
0ca9793
Table.Get SelectorPoints implementation
marino39 Mar 31, 2023
a002c93
Table.Get SelectorPoints implementation -- add one more benchmark
marino39 Mar 31, 2023
9abce7e
Table.Get SelectorRanges implementation
marino39 Apr 4, 2023
0781cc7
Index.Iter unit tests
marino39 Apr 5, 2023
cdcb795
Index.Iter unit tests - PrimaryIndex
marino39 Apr 5, 2023
f38b78a
Index.Iter unit tests - new concept of Point Iter for primary index &…
marino39 Apr 5, 2023
a319514
Index.Iter restore SelectorPoints impl & unit tests
marino39 Apr 6, 2023
3f29335
Index OnInsert/OnUpdate/OnDelete unit tests
marino39 Apr 6, 2023
e8b19d2
Fix: Index.OnUpdate not properly updating indexes
marino39 Apr 6, 2023
40d552c
Update README.md
marino39 Apr 7, 2023
4e25ee3
Update comment in selector.go for SelectorRange and SelectorRanges
marino39 Apr 7, 2023
699851e
Fix: Issue with Query, SelectorRanges and Query.After
marino39 Apr 7, 2023
3bde6d3
Remove no longer valid test case
marino39 Apr 7, 2023
f8a2ab1
Query tests with different selectors and Query.After
marino39 Apr 11, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Table.Get SelectorRanges implementation
  • Loading branch information
marino39 committed Apr 4, 2023
commit 9abce7e0ef8163925f56ff09849bd31137434436
2 changes: 1 addition & 1 deletion batch.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ func (b *_batch) DeleteRange(start []byte, end []byte, opt WriteOptions, _ ...Ba
}

func (b *_batch) Iter(opt *IterOptions, _ ...Batch) Iterator {
return newIteratorFromBatch(b.Batch, opt)
return newIterator(b.Batch, opt)
}

func (b *_batch) Apply(batch Batch, opt WriteOptions) error {
Expand Down
2 changes: 1 addition & 1 deletion bond.go
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ func (db *_db) Iter(opt *IterOptions, batch ...Batch) Iterator {
if batch != nil && len(batch) > 0 && batch[0] != nil {
return batch[0].Iter(opt)
} else {
return newIteratorFromDB(db, opt)
return newIterator(db.pebble, opt)
}
}

Expand Down
106 changes: 81 additions & 25 deletions index.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,55 +179,111 @@ func (idx *Index[T]) Name() string {
// The iterator will return the index keys in the order specified by the index.
// The iterator will return data if created for primary index.
func (idx *Index[T]) Iter(table Table[T], selector Selector[T], optBatch ...Batch) Iterator {
var (
lowerBound []byte
upperBound []byte
)
var iterConstructor Iterationer = table.DB()
if len(optBatch) > 0 {
iterConstructor = optBatch[0]
}

keyBufferPool := table.DB().getKeyBufferPool()

switch selector.Type() {
case SelectorTypePoint:
sel := selector.(SelectorPoint[T])

lowerBound = encodeIndexKey(table, sel.Point(), idx, keyBufferPool.Get()[:0])
upperBound = keySuccessor(lowerBound[0:_KeyPrefixSplitIndex(lowerBound)], keyBufferPool.Get()[:0])
lowerBound := encodeIndexKey(table, sel.Point(), idx, keyBufferPool.Get()[:0])
upperBound := keySuccessor(lowerBound[0:_KeyPrefixSplitIndex(lowerBound)], keyBufferPool.Get()[:0])

releaseBuffers := func() {
keyBufferPool.Put(lowerBound[:0])
keyBufferPool.Put(upperBound[:0])
}

return iterConstructor.Iter(&IterOptions{
IterOptions: pebble.IterOptions{
LowerBound: lowerBound,
UpperBound: upperBound,
},
releaseBufferOnClose: releaseBuffers,
})
case SelectorTypePoints:
sel := selector.(SelectorPoints[T])

var pebbleOpts []*IterOptions
for _, point := range sel.Points() {
lowerBound := encodeIndexKey(table, point, idx, keyBufferPool.Get()[:0])
upperBound := keySuccessor(lowerBound[0:_KeyPrefixSplitIndex(lowerBound)], keyBufferPool.Get()[:0])

releaseBuffers := func() {
keyBufferPool.Put(lowerBound[:0])
keyBufferPool.Put(upperBound[:0])
}

pebbleOpts = append(pebbleOpts, &IterOptions{
IterOptions: pebble.IterOptions{
LowerBound: lowerBound,
UpperBound: upperBound,
},
releaseBufferOnClose: releaseBuffers,
})
}

return newIteratorMulti(iterConstructor, pebbleOpts)
case SelectorTypeRange:
sel := selector.(SelectorRange[T])
low, up := sel.Range()

lowerBound = encodeIndexKey(table, low, idx, keyBufferPool.Get()[:0])
upperBound = encodeIndexKey(table, up, idx, keyBufferPool.Get()[:0])
lowerBound := encodeIndexKey(table, low, idx, keyBufferPool.Get()[:0])
upperBound := encodeIndexKey(table, up, idx, keyBufferPool.Get()[:0])
if bytes.Equal(lowerBound, upperBound) {
upperBound = keySuccessor(lowerBound[0:_KeyPrefixSplitIndex(lowerBound)], upperBound[:0])
} else {
upperBound = keySuccessor(upperBound, upperBound[:0])
}
default:
panic("invalid selector type")
}

releaseBuffers := func() {
keyBufferPool.Put(lowerBound[:0])
keyBufferPool.Put(upperBound[:0])
}
releaseBuffers := func() {
keyBufferPool.Put(lowerBound[:0])
keyBufferPool.Put(upperBound[:0])
}

if len(optBatch) > 0 {
return optBatch[0].Iter(&IterOptions{
IterOptions: pebble.IterOptions{
LowerBound: lowerBound,
UpperBound: upperBound,
},
releaseBufferOnClose: releaseBuffers,
})
} else {
return table.DB().Iter(&IterOptions{
return iterConstructor.Iter(&IterOptions{
IterOptions: pebble.IterOptions{
LowerBound: lowerBound,
UpperBound: upperBound,
},
releaseBufferOnClose: releaseBuffers,
})
case SelectorTypeRanges:
sel := selector.(SelectorRanges[T])

var pebbleOpts []*IterOptions
for _, r := range sel.Ranges() {
low, up := r[0], r[1]

lowerBound := encodeIndexKey(table, low, idx, keyBufferPool.Get()[:0])
upperBound := encodeIndexKey(table, up, idx, keyBufferPool.Get()[:0])
if bytes.Equal(lowerBound, upperBound) {
upperBound = keySuccessor(lowerBound[0:_KeyPrefixSplitIndex(lowerBound)], upperBound[:0])
} else {
upperBound = keySuccessor(upperBound, upperBound[:0])
}

releaseBuffers := func() {
keyBufferPool.Put(lowerBound[:0])
keyBufferPool.Put(upperBound[:0])
}

pebbleOpts = append(pebbleOpts, &IterOptions{
IterOptions: pebble.IterOptions{
LowerBound: lowerBound,
UpperBound: upperBound,
},
releaseBufferOnClose: releaseBuffers,
})
}

return newIteratorMulti(iterConstructor, pebbleOpts)
default:
panic("invalid selector type")
}
}

Expand Down
124 changes: 118 additions & 6 deletions iter.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,18 +26,18 @@ type Iterator interface {
Close() error
}

type _iterConstructor interface {
NewIter(opts *pebble.IterOptions) *pebble.Iterator
}

type _iterator struct {
Iterator

opts *IterOptions
}

func newIteratorFromDB(db *_db, opts *IterOptions) *_iterator {
return &_iterator{Iterator: db.pebble.NewIter(&opts.IterOptions), opts: opts}
}

func newIteratorFromBatch(batch *pebble.Batch, opts *IterOptions) *_iterator {
return &_iterator{Iterator: batch.NewIter(&opts.IterOptions), opts: opts}
func newIterator(itc _iterConstructor, opts *IterOptions) *_iterator {
return &_iterator{Iterator: itc.NewIter(&opts.IterOptions), opts: opts}
}

func (it *_iterator) Close() error {
Expand All @@ -53,3 +53,115 @@ func (it *_iterator) Close() error {
}
return nil
}

type _iteratorMulti struct {
iteratorOptions []*IterOptions
iteratorOptionsIndex int

iteratorConstuctor Iterationer
iterator Iterator
}

func newIteratorMulti(itc Iterationer, opts []*IterOptions) *_iteratorMulti {
return &_iteratorMulti{
iteratorOptions: opts,
iteratorOptionsIndex: 0,
iteratorConstuctor: itc,
iterator: itc.Iter(opts[0]),
}
}

func (it *_iteratorMulti) First() bool {
if it.iteratorOptionsIndex != 0 {
_ = it.iterator.Close()

it.iteratorOptionsIndex = 0
it.iterator = it.iteratorConstuctor.Iter(it.iteratorOptions[it.iteratorOptionsIndex])
}
return it.iterator.First()
}

func (it *_iteratorMulti) Last() bool {
if it.iteratorOptionsIndex != len(it.iteratorOptions)-1 {
_ = it.iterator.Close()

it.iteratorOptionsIndex = len(it.iteratorOptions) - 1
it.iterator = it.iteratorConstuctor.Iter(it.iteratorOptions[it.iteratorOptionsIndex])
}
return it.iterator.Last()
}

func (it *_iteratorMulti) Prev() bool {
if !it.iterator.Prev() {
if it.iteratorOptionsIndex == 0 {
return false
}

_ = it.iterator.Close()

it.iteratorOptionsIndex--
it.iterator = it.iteratorConstuctor.Iter(it.iteratorOptions[it.iteratorOptionsIndex])
return it.iterator.Last()
}
return true
}

func (it *_iteratorMulti) Next() bool {
if !it.iterator.Next() {
if it.iteratorOptionsIndex == len(it.iteratorOptions)-1 {
return false
}

_ = it.iterator.Close()

it.iteratorOptionsIndex++
it.iterator = it.iteratorConstuctor.Iter(it.iteratorOptions[it.iteratorOptionsIndex])
return it.iterator.First()
}
return true
}

func (it *_iteratorMulti) Valid() bool {
return it.iterator.Valid()
}

func (it *_iteratorMulti) Error() error {
return it.iterator.Error()
}

func (it *_iteratorMulti) SeekGE(key []byte) bool {
//TODO implement me
panic("implement me")
}

func (it *_iteratorMulti) SeekPrefixGE(key []byte) bool {
//TODO implement me
panic("implement me")
}

func (it *_iteratorMulti) SeekLT(key []byte) bool {
//TODO implement me
panic("implement me")
}

func (it *_iteratorMulti) Key() []byte {
return it.iterator.Key()
}

func (it *_iteratorMulti) Value() []byte {
return it.iterator.Value()
}

func (it *_iteratorMulti) Close() error {
defer func() {
for _, opts := range it.iteratorOptions {
if opts.releaseBufferOnClose != nil {
opts.releaseBufferOnClose()
}
}
}()

return it.iterator.Close()
}

var _ Iterator = (*_iteratorMulti)(nil)
2 changes: 1 addition & 1 deletion table.go
Original file line number Diff line number Diff line change
Expand Up @@ -894,7 +894,7 @@ func (t *_table[T]) Get(ctx context.Context, sel Selector[T], optBatch ...Batch)
}

return trs, nil
case SelectorTypeRange:
case SelectorTypeRange, SelectorTypeRanges:
var trs []T

err := t.ScanIndex(ctx, t.primaryIndex, sel, &trs, optBatch...)
Expand Down
65 changes: 65 additions & 0 deletions table_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package bond

import (
"context"
"math"
"testing"

"github.com/cockroachdb/pebble"
Expand Down Expand Up @@ -318,6 +319,70 @@ func TestBondTable_Get_Points(t *testing.T) {
assert.Nil(t, tokenBalances[0])
}

func TestBondTable_Get_Ranges(t *testing.T) {
db := setupDatabase()
defer tearDownDatabase(db)

const (
TokenBalanceTableID TableID = 0xC0
)

tokenBalanceTable := NewTable[*TokenBalance](TableOptions[*TokenBalance]{
DB: db,
TableID: TokenBalanceTableID,
TableName: "token_balance",
TablePrimaryKeyFunc: func(builder KeyBuilder, tb *TokenBalance) []byte {
return builder.AddUint64Field(tb.ID).Bytes()
},
Serializer: &serializers.JsonSerializer{},
})
require.NotNil(t, tokenBalanceTable)

// token balances to insert
insertTokenBalances := []*TokenBalance{
{
ID: 1,
AccountID: 1,
ContractAddress: "0xtestContract",
AccountAddress: "0xtestAccount",
Balance: 5,
},
{
ID: 2,
AccountID: 1,
ContractAddress: "0xtestContract",
AccountAddress: "0xtestAccount",
Balance: 5,
},
{
ID: 3,
AccountID: 1,
ContractAddress: "0xtestContract",
AccountAddress: "0xtestAccount",
Balance: 7,
},
}

err := tokenBalanceTable.Insert(context.Background(), insertTokenBalances)
require.NoError(t, err)

expectedTokenBalances := []*TokenBalance{
insertTokenBalances[0],
insertTokenBalances[2],
}

// get token balances with range
tokenBalances, err := tokenBalanceTable.Get(context.Background(), NewSelectorRanges([]*TokenBalance{{ID: 0}, {ID: 1}}, []*TokenBalance{{ID: 3}, {ID: math.MaxUint64}}))
require.NoError(t, err)
require.Equal(t, len(expectedTokenBalances), len(tokenBalances))
assert.Equal(t, expectedTokenBalances, tokenBalances)

// get token balance with non-existing id range
tokenBalances, err = tokenBalanceTable.Get(context.Background(), NewSelectorRanges([]*TokenBalance{{ID: 5}, {ID: 5}}))
require.NoError(t, err)
assert.Equal(t, 0, len(tokenBalances))
}

func TestBondTable_Insert(t *testing.T) {
db := setupDatabase()
defer tearDownDatabase(db)
Expand Down