Skip to content

Commit a725247

Browse files
committed
fix DeleteRange
1 parent 4766421 commit a725247

File tree

4 files changed

+99
-11
lines changed

4 files changed

+99
-11
lines changed

storage/operation/pebbleimpl/writer.go

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
package pebbleimpl
22

33
import (
4+
"fmt"
5+
46
"github.com/cockroachdb/pebble"
57

68
"github.com/onflow/flow-go/storage"
9+
"github.com/onflow/flow-go/storage/operation"
710
op "github.com/onflow/flow-go/storage/operation"
811
)
912

@@ -100,9 +103,19 @@ func (b *ReaderBatchWriter) Delete(key []byte) error {
100103
// DeleteByRange removes all keys with a prefix that falls within the
101104
// range [start, end], both inclusive.
102105
// No errors expected during normal operation
103-
func (b *ReaderBatchWriter) DeleteByRange(_ storage.Reader, startPrefix, endPrefix []byte) error {
106+
func (b *ReaderBatchWriter) DeleteByRange(globalReader storage.Reader, startPrefix, endPrefix []byte) error {
104107
// DeleteRange takes the prefix range with start (inclusive) and end (exclusive, note: not inclusive).
105108
// therefore, we need to increment the endPrefix to make it inclusive.
106109
start, end := storage.StartEndPrefixToLowerUpperBound(startPrefix, endPrefix)
107-
return b.batch.DeleteRange(start, end, pebble.Sync)
110+
if len(end) > 0 {
111+
return b.batch.DeleteRange(start, end, pebble.Sync)
112+
}
113+
114+
return operation.IterateKeysInPrefixRange(startPrefix, endPrefix, func(key []byte) error {
115+
err := b.batch.Delete(key, pebble.Sync)
116+
if err != nil {
117+
return fmt.Errorf("could not add key to delete batch (%v): %w", key, err)
118+
}
119+
return nil
120+
})(globalReader)
108121
}

storage/operation/reads_test.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,18 @@ func TestIterateKeysInPrefixRange(t *testing.T) {
6161
})
6262
}
6363

64+
// TestIterateInvalidRange tests that error should return if startPrefix > endPrefix
65+
func TestIterateInvalidRange(t *testing.T) {
66+
dbtest.RunWithStorages(t, func(t *testing.T, r storage.Reader, withWriter dbtest.WithWriter) {
67+
68+
var found [][]byte
69+
require.Error(t, operation.IterateKeysInPrefixRange([]byte{0x02}, []byte{0x01}, func(key []byte) error {
70+
found = append(found, key)
71+
return nil
72+
})(r))
73+
})
74+
}
75+
6476
func TestIterationBoundary(t *testing.T) {
6577
dbtest.RunWithStorages(t, func(t *testing.T, r storage.Reader, withWriter dbtest.WithWriter) {
6678
// Define the prefix range

storage/operation/writes_test.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,53 @@ func TestRemoveRange(t *testing.T) {
244244
})
245245
}
246246

247+
func TestRemoveFrom(t *testing.T) {
248+
dbtest.RunWithStorages(t, func(t *testing.T, r storage.Reader, withWriter dbtest.WithWriter) {
249+
250+
// Define the prefix
251+
prefix := []byte{0xff}
252+
253+
// Create a range of keys around the boundaries of the prefix
254+
keys := [][]byte{
255+
{0x10, 0x00},
256+
{0xff},
257+
{0xff, 0x00},
258+
{0xff, 0xff},
259+
}
260+
261+
// Keys expected to be in the prefix range
262+
includeStart, includeEnd := 1, 3
263+
264+
// Insert the keys into the storage
265+
require.NoError(t, withWriter(func(writer storage.Writer) error {
266+
for _, key := range keys {
267+
value := []byte{0x00} // value are skipped, doesn't matter
268+
err := operation.Upsert(key, value)(writer)
269+
if err != nil {
270+
return err
271+
}
272+
}
273+
return nil
274+
}))
275+
276+
// Remove the keys in the prefix range
277+
require.NoError(t, withWriter(operation.RemoveByPrefix(r, prefix)))
278+
279+
// Verify that the keys in the prefix range have been removed
280+
for i, key := range keys {
281+
var exists bool
282+
require.NoError(t, operation.Exists(key, &exists)(r))
283+
t.Logf("key %x exists: %t", key, exists)
284+
285+
deleted := includeStart <= i && i <= includeEnd
286+
287+
// An item that was not deleted must exist
288+
require.Equal(t, !deleted, exists,
289+
fmt.Errorf("a key %x should be deleted (%v), but actually exists (%v)", key, deleted, exists))
290+
}
291+
})
292+
}
293+
247294
type Entity struct {
248295
ID uint64
249296
}

storage/operations.go

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,30 @@ import (
55
)
66

77
// Iterator is an interface for iterating over key-value pairs in a storage backend.
8+
// A common usage is:
9+
//
10+
// defer it.Close()
11+
//
12+
// for it.First(); it.Valid(); it.Next() {
13+
// item := it.IterItem()
14+
// }
815
type Iterator interface {
916
// First seeks to the smallest key greater than or equal to the given key.
17+
// This method must be called because it's necessary for the badger implementation
18+
// to move the iteration cursor to the first key in the iteration range.
19+
// This method must be called before calling Valid, Next, IterItem, or Close.
1020
First()
1121

1222
// Valid returns whether the iterator is positioned at a valid key-value pair.
23+
// If Valid returns false, the iterator is done and must be closed.
1324
Valid() bool
1425

1526
// Next advances the iterator to the next key-value pair.
27+
// The next key-value pair might be invalid, so you should call Valid() to check.
1628
Next()
1729

1830
// IterItem returns the current key-value pair, or nil if done.
31+
// A best practice is always to call Valid() before calling IterItem.
1932
IterItem() IterItem
2033

2134
// Close closes the iterator. Iterator must be closed, otherwise it causes memory leak.
@@ -49,7 +62,7 @@ type Reader interface {
4962
// other errors are exceptions
5063
//
5164
// The caller should not modify the contents of the returned slice, but it is
52-
// safe to modify the contents of the argument after Get returns. The
65+
// safe to modify the contents of the `key` argument after Get returns. The
5366
// returned slice will remain valid until the returned Closer is closed. On
5467
// success, the caller MUST call closer.Close() or a memory leak will occur.
5568
Get(key []byte) (value []byte, closer io.Closer, err error)
@@ -63,7 +76,7 @@ type Reader interface {
6376
}
6477

6578
// Writer is an interface for batch writing to a storage backend.
66-
// It cannot be used concurrently for writing.
79+
// One Writer instance cannot be used concurrently by multiple goroutines.
6780
type Writer interface {
6881
// Set sets the value for the given key. It overwrites any previous value
6982
// for that key; a DB is not a multi-map.
@@ -117,18 +130,21 @@ func OnCommitSucceed(b ReaderBatchWriter, onSuccessFn func()) {
117130
})
118131
}
119132

133+
// StartEndPrefixToLowerUpperBound returns the lower and upper bounds for a range of keys
134+
// specified by the start and end prefixes.
135+
// the lower and upper bounds are used for the key iteration.
136+
// The return value lowerBound specifies the smallest key to iterate and it's inclusive.
137+
// The return value upperBound specifies the largest key to iterate and it's exclusive (not inclusive)
138+
// in order to match all keys prefixed with `endPrefix`, we increment the bytes of `endPrefix` by 1,
139+
// for instance, to iterate keys between "hello" and "world",
140+
// we use "hello" as LowerBound, "worle" as UpperBound, so that "world", "world1", "worldffff...ffff"
141+
// will all be included.
120142
func StartEndPrefixToLowerUpperBound(startPrefix, endPrefix []byte) (lowerBound, upperBound []byte) {
121-
// Return value lowerBound specifies the smallest key to iterate and it's inclusive.
122-
// Return value upperBound specifies the largest key to iterate and it's exclusive (not inclusive)
123-
// in order to match all keys prefixed with `endPrefix`, we increment the bytes of `endPrefix` by 1,
124-
// for instance, to iterate keys between "hello" and "world",
125-
// we use "hello" as LowerBound, "worle" as UpperBound, so that "world", "world1", "worldffff...ffff"
126-
// will all be included.
127143
return startPrefix, PrefixUpperBound(endPrefix)
128144
}
129145

130146
// PrefixUpperBound returns a key K such that all possible keys beginning with the input prefix
131-
// sort lower than K according to the byte-wise lexicographic key ordering used by Pebble.
147+
// sort lower than K according to the byte-wise lexicographic key ordering.
132148
// This is used to define an upper bound for iteration, when we want to iterate over
133149
// all keys beginning with a given prefix.
134150
// referred to https://pkg.go.dev/github.com/cockroachdb/pebble#example-Iterator-PrefixIteration

0 commit comments

Comments
 (0)