Skip to content

Commit ea2487e

Browse files
authored
feat: Typed bucket (#3255)
1 parent fc3b112 commit ea2487e

File tree

2 files changed

+241
-0
lines changed

2 files changed

+241
-0
lines changed

db/typed/bucket.go

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package typed
2+
3+
import (
4+
"github.com/NethermindEth/juno/db"
5+
"github.com/NethermindEth/juno/db/typed/key"
6+
"github.com/NethermindEth/juno/db/typed/value"
7+
)
8+
9+
type Bucket[K any, V any, KS key.Serializer[K], VS value.Serializer[V]] struct {
10+
db.Bucket
11+
}
12+
13+
func NewBucket[K any, V any, KS key.Serializer[K], VS value.Serializer[V]](
14+
bucket db.Bucket,
15+
keySerializer KS,
16+
valueSerializer VS,
17+
) Bucket[K, V, KS, VS] {
18+
return Bucket[K, V, KS, VS]{
19+
Bucket: bucket,
20+
}
21+
}
22+
23+
func (b Bucket[K, V, KS, VS]) RawKey() Bucket[[]byte, V, key.BytesSerializer, VS] {
24+
return Bucket[[]byte, V, key.BytesSerializer, VS](b)
25+
}
26+
27+
func (b Bucket[K, V, KS, VS]) RawValue() Bucket[K, []byte, KS, value.BytesSerializer] {
28+
return Bucket[K, []byte, KS, value.BytesSerializer](b)
29+
}
30+
31+
func (b Bucket[K, V, KS, VS]) Has(database db.KeyValueReader, key K) (bool, error) {
32+
return database.Has(b.Key(KS{}.Marshal(key)))
33+
}
34+
35+
func (b Bucket[K, V, KS, VS]) Get(database db.KeyValueReader, key K) (V, error) {
36+
var value V
37+
err := database.Get(b.Key(KS{}.Marshal(key)), func(data []byte) error {
38+
return VS{}.Unmarshal(data, &value)
39+
})
40+
return value, err
41+
}
42+
43+
func (b Bucket[K, V, KS, VS]) Put(database db.KeyValueWriter, key K, value *V) error {
44+
data, err := VS{}.Marshal(value)
45+
if err != nil {
46+
return err
47+
}
48+
return database.Put(b.Key(KS{}.Marshal(key)), data)
49+
}
50+
51+
func (b Bucket[K, V, KS, VS]) Delete(database db.KeyValueWriter, key K) error {
52+
return database.Delete(b.Key(KS{}.Marshal(key)))
53+
}

db/typed/bucket_test.go

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
package typed_test
2+
3+
import (
4+
"fmt"
5+
"math/rand/v2"
6+
"testing"
7+
8+
"github.com/NethermindEth/juno/core/felt"
9+
"github.com/NethermindEth/juno/db"
10+
"github.com/NethermindEth/juno/db/memory"
11+
"github.com/NethermindEth/juno/db/typed"
12+
"github.com/NethermindEth/juno/db/typed/key"
13+
"github.com/NethermindEth/juno/db/typed/value"
14+
"github.com/stretchr/testify/require"
15+
)
16+
17+
type testCase struct {
18+
name string
19+
del int
20+
update int
21+
insert int
22+
}
23+
24+
var bucket = typed.NewBucket(
25+
db.Bucket(0),
26+
key.Uint64,
27+
value.Felt,
28+
)
29+
30+
func del(
31+
t *testing.T,
32+
txn db.KeyValueWriter,
33+
del int,
34+
expected map[uint64]felt.Felt,
35+
) (deleted []uint64) {
36+
t.Helper()
37+
t.Run(fmt.Sprintf("Delete %d entries", del), func(t *testing.T) {
38+
deleted = make([]uint64, 0, del)
39+
for key := range expected {
40+
delete(expected, key)
41+
require.NoError(t, bucket.Delete(txn, key))
42+
43+
deleted = append(deleted, key)
44+
if len(deleted) >= del {
45+
break
46+
}
47+
}
48+
})
49+
return deleted
50+
}
51+
52+
func update(
53+
t *testing.T,
54+
txn db.KeyValueWriter,
55+
update int,
56+
expected map[uint64]felt.Felt,
57+
) {
58+
t.Helper()
59+
t.Run(fmt.Sprintf("Update %d entries", update), func(t *testing.T) {
60+
updated := 0
61+
for key := range expected {
62+
value := felt.Random[felt.Felt]()
63+
expected[key] = value
64+
require.NoError(t, bucket.Put(txn, key, &value))
65+
66+
updated++
67+
if updated >= update {
68+
break
69+
}
70+
}
71+
})
72+
}
73+
74+
func insert(
75+
t *testing.T,
76+
txn db.KeyValueWriter,
77+
insert int,
78+
expected map[uint64]felt.Felt,
79+
) {
80+
t.Helper()
81+
t.Run(fmt.Sprintf("Insert %d entries", insert), func(t *testing.T) {
82+
for range insert {
83+
key := rand.Uint64()
84+
for {
85+
if _, exists := expected[key]; !exists {
86+
break
87+
}
88+
key = rand.Uint64()
89+
}
90+
value := felt.Random[felt.Felt]()
91+
expected[key] = value
92+
require.NoError(t, bucket.Put(txn, key, &value))
93+
}
94+
})
95+
}
96+
97+
func assertExists(t *testing.T, txn db.KeyValueReader, expected map[uint64]felt.Felt) {
98+
for key, value := range expected {
99+
has, err := bucket.Has(txn, key)
100+
require.NoError(t, err)
101+
require.True(t, has)
102+
103+
got, err := bucket.Get(txn, key)
104+
require.NoError(t, err)
105+
require.Equal(t, value, got)
106+
}
107+
}
108+
109+
func assertNotExists(t *testing.T, txn db.KeyValueReader, keys []uint64) {
110+
for _, key := range keys {
111+
has, err := bucket.Has(txn, key)
112+
require.NoError(t, err)
113+
require.False(t, has)
114+
115+
_, err = bucket.Get(txn, key)
116+
require.ErrorIs(t, err, db.ErrKeyNotFound)
117+
}
118+
}
119+
120+
func TestBucket(t *testing.T) {
121+
cases := []testCase{
122+
{
123+
name: "Insert first entries",
124+
insert: 10000,
125+
},
126+
{
127+
name: "Update-only",
128+
update: 1000,
129+
},
130+
{
131+
name: "Delete-only",
132+
del: 1000,
133+
},
134+
{
135+
name: "Insert and update entries",
136+
insert: 1000,
137+
update: 1000,
138+
},
139+
{
140+
name: "Mix writes",
141+
insert: 1000,
142+
update: 1000,
143+
del: 1000,
144+
},
145+
}
146+
147+
database := memory.New()
148+
expected := make(map[uint64]felt.Felt)
149+
150+
for _, tc := range cases {
151+
t.Run(tc.name, func(t *testing.T) {
152+
var deleted []uint64
153+
err := database.Update(func(txn db.IndexedBatch) error {
154+
if tc.del > 0 {
155+
deleted = del(t, txn, tc.del, expected)
156+
}
157+
if tc.update > 0 {
158+
update(t, txn, tc.update, expected)
159+
}
160+
if tc.insert > 0 {
161+
insert(t, txn, tc.insert, expected)
162+
}
163+
return nil
164+
})
165+
require.NoError(t, err)
166+
assertNotExists(t, database, deleted)
167+
assertExists(t, database, expected)
168+
})
169+
}
170+
171+
t.Run("RawKey and RawValue", func(t *testing.T) {
172+
rawBucket := bucket.RawKey().RawValue()
173+
174+
for k, v := range expected {
175+
keyBytes := key.Uint64.Marshal(k)
176+
has, err := rawBucket.Has(database, keyBytes)
177+
require.NoError(t, err)
178+
require.True(t, has)
179+
180+
expectedBytes, err := value.Felt.Marshal(&v)
181+
require.NoError(t, err)
182+
183+
actualBytes, err := rawBucket.Get(database, keyBytes)
184+
require.NoError(t, err)
185+
require.Equal(t, expectedBytes, actualBytes)
186+
}
187+
})
188+
}

0 commit comments

Comments
 (0)