Skip to content

Commit a78a26d

Browse files
authored
Merge pull request #2191 from CortexFoundation/dev
add DeleteRange feature
2 parents 7bc1713 + 16d3cfa commit a78a26d

File tree

10 files changed

+162
-2
lines changed

10 files changed

+162
-2
lines changed

core/rawdb/table.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,12 @@ func (t *table) Delete(key []byte) error {
129129
return t.db.Delete(append([]byte(t.prefix), key...))
130130
}
131131

132+
// DeleteRange deletes all of the keys (and values) in the range [start,end)
133+
// (inclusive on start, exclusive on end).
134+
func (t *table) DeleteRange(start, end []byte) error {
135+
return t.db.DeleteRange(append([]byte(t.prefix), start...), append([]byte(t.prefix), end...))
136+
}
137+
132138
// NewIterator creates a binary-alphabetical iterator over a subset
133139
// of database content with a particular key prefix, starting at a particular
134140
// initial key (or after, if it does not exist).

ctxcdb/database.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,13 @@ type KeyValueWriter interface {
3737
Delete(key []byte) error
3838
}
3939

40+
// KeyValueRangeDeleter wraps the DeleteRange method of a backing data store.
41+
type KeyValueRangeDeleter interface {
42+
// DeleteRange deletes all of the keys (and values) in the range [start,end)
43+
// (inclusive on start, exclusive on end).
44+
DeleteRange(start, end []byte) error
45+
}
46+
4047
// KeyValueStater wraps the Stat method of a backing data store.
4148
type KeyValueStater interface {
4249
// Stat returns a particular internal stat of the database.
@@ -61,6 +68,7 @@ type KeyValueStore interface {
6168
KeyValueReader
6269
KeyValueWriter
6370
KeyValueStater
71+
KeyValueRangeDeleter
6472
Batcher
6573
Iteratee
6674
Compacter
@@ -154,6 +162,7 @@ type Reader interface {
154162
// immutable ancient data.
155163
type Writer interface {
156164
KeyValueWriter
165+
KeyValueRangeDeleter
157166
AncientWriter
158167
}
159168

ctxcdb/dbtest/testsuite.go

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"crypto/rand"
2222
"reflect"
2323
"sort"
24+
"strconv"
2425
"testing"
2526

2627
"github.com/CortexFoundation/CortexTheseus/ctxcdb"
@@ -403,6 +404,64 @@ func TestDatabaseSuite(t *testing.T, New func() ctxcdb.KeyValueStore) {
403404
t.Fatalf("expected error on batch.Write after Close")
404405
}
405406
})
407+
408+
t.Run("DeleteRange", func(t *testing.T) {
409+
db := New()
410+
defer db.Close()
411+
412+
addRange := func(start, stop int) {
413+
for i := start; i <= stop; i++ {
414+
db.Put([]byte(strconv.Itoa(i)), nil)
415+
}
416+
}
417+
418+
checkRange := func(start, stop int, exp bool) {
419+
for i := start; i <= stop; i++ {
420+
has, _ := db.Has([]byte(strconv.Itoa(i)))
421+
if has && !exp {
422+
t.Fatalf("unexpected key %d", i)
423+
}
424+
if !has && exp {
425+
t.Fatalf("missing expected key %d", i)
426+
}
427+
}
428+
}
429+
430+
addRange(1, 9)
431+
db.DeleteRange([]byte("9"), []byte("1"))
432+
checkRange(1, 9, true)
433+
db.DeleteRange([]byte("5"), []byte("5"))
434+
checkRange(1, 9, true)
435+
db.DeleteRange([]byte("5"), []byte("50"))
436+
checkRange(1, 4, true)
437+
checkRange(5, 5, false)
438+
checkRange(6, 9, true)
439+
db.DeleteRange([]byte(""), []byte("a"))
440+
checkRange(1, 9, false)
441+
442+
addRange(1, 999)
443+
db.DeleteRange([]byte("12345"), []byte("54321"))
444+
checkRange(1, 1, true)
445+
checkRange(2, 5, false)
446+
checkRange(6, 12, true)
447+
checkRange(13, 54, false)
448+
checkRange(55, 123, true)
449+
checkRange(124, 543, false)
450+
checkRange(544, 999, true)
451+
452+
addRange(1, 999)
453+
db.DeleteRange([]byte("3"), []byte("7"))
454+
checkRange(1, 2, true)
455+
checkRange(3, 6, false)
456+
checkRange(7, 29, true)
457+
checkRange(30, 69, false)
458+
checkRange(70, 299, true)
459+
checkRange(300, 699, false)
460+
checkRange(700, 999, true)
461+
462+
db.DeleteRange([]byte(""), []byte("a"))
463+
checkRange(1, 999, false)
464+
})
406465
}
407466

408467
// BenchDatabaseSuite runs a suite of benchmarks against a KeyValueStore database
@@ -498,6 +557,29 @@ func BenchDatabaseSuite(b *testing.B, New func() ctxcdb.KeyValueStore) {
498557
benchBatchWrite(b, keys, vals)
499558
})
500559
})
560+
b.Run("DeleteRange", func(b *testing.B) {
561+
benchDeleteRange := func(b *testing.B, count int) {
562+
db := New()
563+
defer db.Close()
564+
565+
for i := 0; i < count; i++ {
566+
db.Put([]byte(strconv.Itoa(i)), nil)
567+
}
568+
b.ResetTimer()
569+
b.ReportAllocs()
570+
571+
db.DeleteRange([]byte("0"), []byte("999999999"))
572+
}
573+
b.Run("DeleteRange100", func(b *testing.B) {
574+
benchDeleteRange(b, 100)
575+
})
576+
b.Run("DeleteRange1k", func(b *testing.B) {
577+
benchDeleteRange(b, 1000)
578+
})
579+
b.Run("DeleteRange10k", func(b *testing.B) {
580+
benchDeleteRange(b, 10000)
581+
})
582+
})
501583
}
502584

503585
func iterateKeys(it ctxcdb.Iterator) []string {

ctxcdb/leveldb/leveldb.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
package leveldb
2222

2323
import (
24+
"bytes"
2425
"fmt"
2526
"strings"
2627
"sync"
@@ -207,6 +208,36 @@ func (db *Database) Delete(key []byte) error {
207208
return db.db.Delete(key, nil)
208209
}
209210

211+
var ErrTooManyKeys = errors.New("too many keys in deleted range")
212+
213+
// DeleteRange deletes all of the keys (and values) in the range [start,end)
214+
// (inclusive on start, exclusive on end).
215+
// Note that this is a fallback implementation as leveldb does not natively
216+
// support range deletion. It can be slow and therefore the number of deleted
217+
// keys is limited in order to avoid blocking for a very long time.
218+
// ErrTooManyKeys is returned if the range has only been partially deleted.
219+
// In this case the caller can repeat the call until it finally succeeds.
220+
func (db *Database) DeleteRange(start, end []byte) error {
221+
batch := db.NewBatch()
222+
it := db.NewIterator(nil, start)
223+
defer it.Release()
224+
225+
var count int
226+
for it.Next() && bytes.Compare(end, it.Key()) > 0 {
227+
count++
228+
if count > 10000 { // should not block for more than a second
229+
if err := batch.Write(); err != nil {
230+
return err
231+
}
232+
return ErrTooManyKeys
233+
}
234+
if err := batch.Delete(it.Key()); err != nil {
235+
return err
236+
}
237+
}
238+
return batch.Write()
239+
}
240+
210241
// NewBatch creates a write-only key-value store that buffers changes to its host
211242
// database until a final write is called.
212243
func (db *Database) NewBatch() ctxcdb.Batch {

ctxcdb/memorydb/memorydb.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
package memorydb
1919

2020
import (
21+
"bytes"
2122
"errors"
2223
"sort"
2324
"strings"
@@ -125,6 +126,20 @@ func (db *Database) Delete(key []byte) error {
125126
return nil
126127
}
127128

129+
// DeleteRange deletes all of the keys (and values) in the range [start,end)
130+
// (inclusive on start, exclusive on end).
131+
func (db *Database) DeleteRange(start, end []byte) error {
132+
it := db.NewIterator(nil, start)
133+
defer it.Release()
134+
135+
for it.Next() && bytes.Compare(end, it.Key()) > 0 {
136+
if err := db.Delete(it.Key()); err != nil {
137+
return err
138+
}
139+
}
140+
return nil
141+
}
142+
128143
// NewBatch creates a write-only key-value store that buffers changes to its host
129144
// database until a final write is called.
130145
func (db *Database) NewBatch() ctxcdb.Batch {

ctxcdb/pebble/pebble.go

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -324,7 +324,18 @@ func (d *Database) Delete(key []byte) error {
324324
if d.closed {
325325
return pebble.ErrClosed
326326
}
327-
return d.db.Delete(key, nil)
327+
return d.db.Delete(key, d.writeOptions)
328+
}
329+
330+
// DeleteRange deletes all of the keys (and values) in the range [start,end)
331+
// (inclusive on start, exclusive on end).
332+
func (d *Database) DeleteRange(start, end []byte) error {
333+
d.quitLock.RLock()
334+
defer d.quitLock.RUnlock()
335+
if d.closed {
336+
return pebble.ErrClosed
337+
}
338+
return d.db.DeleteRange(start, end, d.writeOptions)
328339
}
329340

330341
// NewBatch creates a write-only key-value store that buffers changes to its host

ctxcdb/remotedb/remotedb.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,10 @@ func (db *Database) Delete(key []byte) error {
9494
panic("not supported")
9595
}
9696

97+
func (db *Database) DeleteRange(start, end []byte) error {
98+
panic("not supported")
99+
}
100+
97101
func (db *Database) ModifyAncients(f func(ctxcdb.AncientWriteOp) error) (int64, error) {
98102
panic("not supported")
99103
}

tests/fuzzers/stacktrie/trie_fuzzer.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ type spongeDb struct {
6565
func (s *spongeDb) Has(key []byte) (bool, error) { panic("implement me") }
6666
func (s *spongeDb) Get(key []byte) ([]byte, error) { return nil, errors.New("no such elem") }
6767
func (s *spongeDb) Delete(key []byte) error { panic("implement me") }
68+
func (s *spongeDb) DeleteRange(start, end []byte) error { panic("implement me") }
6869
func (s *spongeDb) NewBatch() ctxcdb.Batch { return &spongeBatch{s} }
6970
func (s *spongeDb) NewBatchWithSize(size int) ctxcdb.Batch { return &spongeBatch{s} }
7071
func (s *spongeDb) NewSnapshot() (ctxcdb.Snapshot, error) { panic("implement me") }

trie/iterator_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -489,7 +489,7 @@ func (l *loggingDb) Put(key []byte, value []byte) error {
489489
func (l *loggingDb) Delete(key []byte) error {
490490
return l.backend.Delete(key)
491491
}
492-
492+
func (s *loggingDb) DeleteRange(start, end []byte) error { panic("implement me") }
493493
func (l *loggingDb) NewBatch() ctxcdb.Batch {
494494
return l.backend.NewBatch()
495495
}

trie/trie_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -740,6 +740,7 @@ func (s *spongeDb) NewBatch() ctxcdb.Batch { return &spongeBat
740740
func (s *spongeDb) NewBatchWithSize(size int) ctxcdb.Batch { return &spongeBatch{s} }
741741
func (s *spongeDb) NewSnapshot() (ctxcdb.Snapshot, error) { panic("implement me") }
742742
func (s *spongeDb) Stat(property string) (string, error) { panic("implement me") }
743+
func (s *spongeDb) DeleteRange(start, end []byte) error { panic("implement me") }
743744
func (s *spongeDb) Compact(start []byte, limit []byte) error { panic("implement me") }
744745
func (s *spongeDb) Close() error { return nil }
745746
func (s *spongeDb) Put(key []byte, value []byte) error {

0 commit comments

Comments
 (0)