Skip to content

Commit f527723

Browse files
authored
chunk, storage: add HasMulti to chunk.Store (ethersphere#1686)
1 parent ca56afd commit f527723

File tree

8 files changed

+187
-1
lines changed

8 files changed

+187
-1
lines changed

chunk/chunk.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,7 @@ type Store interface {
246246
GetMulti(ctx context.Context, mode ModeGet, addrs ...Address) (ch []Chunk, err error)
247247
Put(ctx context.Context, mode ModePut, chs ...Chunk) (exist []bool, err error)
248248
Has(ctx context.Context, addr Address) (yes bool, err error)
249+
HasMulti(ctx context.Context, addrs ...Address) (yes []bool, err error)
249250
Set(ctx context.Context, mode ModeSet, addr Address) (err error)
250251
LastPullSubscriptionBinID(bin uint8) (id uint64, err error)
251252
SubscribePull(ctx context.Context, bin uint8, since, until uint64) (c <-chan Descriptor, stop func())

shed/index.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,28 @@ func (f Index) Has(keyFields Item) (bool, error) {
189189
return f.db.Has(key)
190190
}
191191

192+
// HasMulti accepts multiple multiple key fields represented as Item to check if
193+
// there this Item's encoded key is stored in the index for each of them.
194+
func (f Index) HasMulti(items ...Item) ([]bool, error) {
195+
have := make([]bool, len(items))
196+
snapshot, err := f.db.ldb.GetSnapshot()
197+
if err != nil {
198+
return nil, err
199+
}
200+
defer snapshot.Release()
201+
for i, keyFields := range items {
202+
key, err := f.encodeKeyFunc(keyFields)
203+
if err != nil {
204+
return nil, err
205+
}
206+
have[i], err = snapshot.Has(key, nil)
207+
if err != nil {
208+
return nil, err
209+
}
210+
}
211+
return have, nil
212+
}
213+
192214
// Put accepts Item to encode information from it
193215
// and save it to the database.
194216
func (f Index) Put(i Item) (err error) {

shed/index_test.go

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1022,3 +1022,85 @@ func TestIncByteSlice(t *testing.T) {
10221022
}
10231023
}
10241024
}
1025+
1026+
// TestIndex_HasMulti validates that HasMulti returns a correct
1027+
// slice of booleans for provided Items.
1028+
func TestIndex_HasMulti(t *testing.T) {
1029+
db, cleanupFunc := newTestDB(t)
1030+
defer cleanupFunc()
1031+
1032+
index, err := db.NewIndex("retrieval", retrievalIndexFuncs)
1033+
if err != nil {
1034+
t.Fatal(err)
1035+
}
1036+
1037+
items := []Item{
1038+
{
1039+
Address: []byte("hash-01"),
1040+
Data: []byte("data94"),
1041+
},
1042+
{
1043+
Address: []byte("hash-03"),
1044+
Data: []byte("data33"),
1045+
},
1046+
{
1047+
Address: []byte("hash-05"),
1048+
Data: []byte("data55"),
1049+
},
1050+
{
1051+
Address: []byte("hash-02"),
1052+
Data: []byte("data21"),
1053+
},
1054+
{
1055+
Address: []byte("hash-06"),
1056+
Data: []byte("data8"),
1057+
},
1058+
}
1059+
missingItem := Item{
1060+
Address: []byte("hash-10"),
1061+
Data: []byte("data0"),
1062+
}
1063+
1064+
batch := new(leveldb.Batch)
1065+
for _, i := range items {
1066+
index.PutInBatch(batch, i)
1067+
}
1068+
err = db.WriteBatch(batch)
1069+
if err != nil {
1070+
t.Fatal(err)
1071+
}
1072+
1073+
got, err := index.HasMulti(items[0])
1074+
if err != nil {
1075+
t.Fatal(err)
1076+
}
1077+
if !got[0] {
1078+
t.Error("first item not found")
1079+
}
1080+
1081+
got, err = index.HasMulti(missingItem)
1082+
if err != nil {
1083+
t.Fatal(err)
1084+
}
1085+
if got[0] {
1086+
t.Error("missing item found")
1087+
}
1088+
1089+
got, err = index.HasMulti(items...)
1090+
if err != nil {
1091+
t.Fatal(err)
1092+
}
1093+
want := []bool{true, true, true, true, true}
1094+
if fmt.Sprint(got) != fmt.Sprint(want) {
1095+
t.Errorf("got %v, want %v", got, want)
1096+
}
1097+
1098+
got, err = index.HasMulti(append(items, missingItem)...)
1099+
if err != nil {
1100+
t.Fatal(err)
1101+
}
1102+
want = []bool{true, true, true, true, true, false}
1103+
if fmt.Sprint(got) != fmt.Sprint(want) {
1104+
t.Errorf("got %v, want %v", got, want)
1105+
}
1106+
}

storage/common_test.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,17 @@ func (m *MapChunkStore) Has(ctx context.Context, ref Address) (has bool, err err
270270
return has, nil
271271
}
272272

273+
func (m *MapChunkStore) HasMulti(ctx context.Context, refs ...Address) (have []bool, err error) {
274+
m.mu.RLock()
275+
defer m.mu.RUnlock()
276+
277+
have = make([]bool, len(refs))
278+
for i, ref := range refs {
279+
_, have[i] = m.chunks[ref.Hex()]
280+
}
281+
return have, nil
282+
}
283+
273284
func (m *MapChunkStore) Set(ctx context.Context, mode chunk.ModeSet, addr chunk.Address) (err error) {
274285
return nil
275286
}

storage/localstore/localstore.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -427,6 +427,18 @@ func addressToItem(addr chunk.Address) shed.Item {
427427
}
428428
}
429429

430+
// addressesToItems constructs a slice of Items with only
431+
// addresses set on them.
432+
func addressesToItems(addrs ...chunk.Address) []shed.Item {
433+
items := make([]shed.Item, len(addrs))
434+
for i, addr := range addrs {
435+
items[i] = shed.Item{
436+
Address: addr,
437+
}
438+
}
439+
return items
440+
}
441+
430442
// now is a helper function that returns a current unix timestamp
431443
// in UTC timezone.
432444
// It is set in the init function for usage in production, and

storage/localstore/mode_has.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,18 @@ func (db *DB) Has(ctx context.Context, addr chunk.Address) (bool, error) {
3737
}
3838
return has, err
3939
}
40+
41+
// HasMulti returns a slice of booleans which represent if the provided chunks
42+
// are stored in database.
43+
func (db *DB) HasMulti(ctx context.Context, addrs ...chunk.Address) ([]bool, error) {
44+
metricName := "localstore.HasMulti"
45+
46+
metrics.GetOrRegisterCounter(metricName, nil).Inc(1)
47+
defer totalTimeMetric(metricName, time.Now())
48+
49+
have, err := db.retrievalDataIndex.HasMulti(addressesToItems(addrs...)...)
50+
if err != nil {
51+
metrics.GetOrRegisterCounter(metricName+".error", nil).Inc(1)
52+
}
53+
return have, err
54+
}

storage/localstore/mode_has_test.go

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,15 @@ package localstore
1818

1919
import (
2020
"context"
21+
"fmt"
22+
"math/rand"
2123
"testing"
24+
"time"
2225

2326
"github.com/ethersphere/swarm/chunk"
2427
)
2528

26-
// TestHas validates that Hasser is returning true for
29+
// TestHas validates that Has method is returning true for
2730
// the stored chunk and false for one that is not stored.
2831
func TestHas(t *testing.T) {
2932
db, cleanupFunc := newTestDB(t, nil)
@@ -54,3 +57,38 @@ func TestHas(t *testing.T) {
5457
t.Error("unexpected chunk is found")
5558
}
5659
}
60+
61+
// TestHasMulti validates that HasMulti method is returning correct boolean
62+
// slice for stored chunks.
63+
func TestHasMulti(t *testing.T) {
64+
r := rand.New(rand.NewSource(time.Now().UnixNano()))
65+
for _, tc := range multiChunkTestCases {
66+
t.Run(tc.name, func(t *testing.T) {
67+
db, cleanupFunc := newTestDB(t, nil)
68+
defer cleanupFunc()
69+
70+
chunks := generateTestRandomChunks(tc.count)
71+
want := make([]bool, tc.count)
72+
73+
for i, ch := range chunks {
74+
if r.Intn(2) == 0 {
75+
// randomly exclude half of the chunks
76+
continue
77+
}
78+
_, err := db.Put(context.Background(), chunk.ModePutUpload, ch)
79+
if err != nil {
80+
t.Fatal(err)
81+
}
82+
want[i] = true
83+
}
84+
85+
got, err := db.HasMulti(context.Background(), chunkAddresses(chunks)...)
86+
if err != nil {
87+
t.Fatal(err)
88+
}
89+
if fmt.Sprint(got) != fmt.Sprint(want) {
90+
t.Errorf("got %v, want %v", got, want)
91+
}
92+
})
93+
}
94+
}

storage/types.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,11 @@ func (f *FakeChunkStore) Has(_ context.Context, ref Address) (bool, error) {
227227
panic("FakeChunkStore doesn't support Has")
228228
}
229229

230+
// HasMulti doesn't do anything it is just here to implement ChunkStore
231+
func (f *FakeChunkStore) HasMulti(_ context.Context, refs ...Address) ([]bool, error) {
232+
panic("FakeChunkStore doesn't support HasMulti")
233+
}
234+
230235
// Get doesn't store anything it is just here to implement ChunkStore
231236
func (f *FakeChunkStore) Get(_ context.Context, _ chunk.ModeGet, ref Address) (Chunk, error) {
232237
panic("FakeChunkStore doesn't support Get")

0 commit comments

Comments
 (0)