From 7590e919145f9db3da505fbe6bb07fb8566405ae Mon Sep 17 00:00:00 2001 From: Aleksandr Bezobchuk Date: Mon, 23 Oct 2023 12:56:14 -0400 Subject: [PATCH] feat(store/v2): support reverse iteration for PebbleDB (#18193) --- store/storage/pebbledb/db.go | 24 ++++++++++++++-- store/storage/pebbledb/db_test.go | 13 --------- store/storage/pebbledb/iterator.go | 44 ++++++++++++++++++++++++++---- 3 files changed, 60 insertions(+), 21 deletions(-) diff --git a/store/storage/pebbledb/db.go b/store/storage/pebbledb/db.go index 910a9d347e..9203d81c58 100644 --- a/store/storage/pebbledb/db.go +++ b/store/storage/pebbledb/db.go @@ -182,11 +182,31 @@ func (db *Database) Iterator(storeKey string, version uint64, start, end []byte) return nil, fmt.Errorf("failed to create PebbleDB iterator: %w", err) } - return newPebbleDBIterator(itr, storePrefix(storeKey), start, end, version), nil + return newPebbleDBIterator(itr, storePrefix(storeKey), start, end, version, false), nil } func (db *Database) ReverseIterator(storeKey string, version uint64, start, end []byte) (store.Iterator, error) { - panic("not implemented!") + if (start != nil && len(start) == 0) || (end != nil && len(end) == 0) { + return nil, store.ErrKeyEmpty + } + + if start != nil && end != nil && bytes.Compare(start, end) > 0 { + return nil, store.ErrStartAfterEnd + } + + lowerBound := MVCCEncode(prependStoreKey(storeKey, start), 0) + + var upperBound []byte + if end != nil { + upperBound = MVCCEncode(prependStoreKey(storeKey, end), 0) + } + + itr, err := db.storage.NewIter(&pebble.IterOptions{LowerBound: lowerBound, UpperBound: upperBound}) + if err != nil { + return nil, fmt.Errorf("failed to create PebbleDB iterator: %w", err) + } + + return newPebbleDBIterator(itr, storePrefix(storeKey), start, end, version, true), nil } func storePrefix(storeKey string) []byte { diff --git a/store/storage/pebbledb/db_test.go b/store/storage/pebbledb/db_test.go index 43178c209e..9465ad55f4 100644 --- a/store/storage/pebbledb/db_test.go +++ b/store/storage/pebbledb/db_test.go @@ -3,17 +3,12 @@ package pebbledb import ( "testing" - "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" "cosmossdk.io/store/v2" "cosmossdk.io/store/v2/storage" ) -const ( - storeKey1 = "store1" -) - func TestStorageTestSuite(t *testing.T) { s := &storage.StorageTestSuite{ NewDB: func(dir string) (store.VersionedDatabase, error) { @@ -26,11 +21,3 @@ func TestStorageTestSuite(t *testing.T) { } suite.Run(t, s) } - -func TestDatabase_ReverseIterator(t *testing.T) { - db, err := New(t.TempDir()) - require.NoError(t, err) - defer db.Close() - - require.Panics(t, func() { _, _ = db.ReverseIterator(storeKey1, 1, []byte("key000"), nil) }) -} diff --git a/store/storage/pebbledb/iterator.go b/store/storage/pebbledb/iterator.go index 9c301bf840..6990ef681f 100644 --- a/store/storage/pebbledb/iterator.go +++ b/store/storage/pebbledb/iterator.go @@ -26,14 +26,21 @@ type iterator struct { prefix, start, end []byte version uint64 valid bool + reverse bool } -func newPebbleDBIterator(src *pebble.Iterator, prefix, mvccStart, mvccEnd []byte, version uint64) *iterator { +func newPebbleDBIterator(src *pebble.Iterator, prefix, mvccStart, mvccEnd []byte, version uint64, reverse bool) *iterator { // move the underlying PebbleDB iterator to the first key - valid := src.First() + var valid bool + if reverse { + valid = src.Last() + } else { + valid = src.First() + } + if valid { - // The first key may not represent the desired target version, so move the - // cursor to the correct location. + // The first key may not represent the desired target version, so seek to + // the correct location by moving the cursor to the first key < version + 1. firstKey, _, ok := SplitMVCCKey(src.Key()) if !ok { // XXX: This should not happen as that would indicate we have a malformed @@ -51,6 +58,7 @@ func newPebbleDBIterator(src *pebble.Iterator, prefix, mvccStart, mvccEnd []byte end: mvccEnd, version: version, valid: valid, + reverse: reverse, } // The cursor might now be pointing at a key/value pair that is tombstoned. @@ -96,10 +104,27 @@ func (itr *iterator) Value() []byte { } func (itr *iterator) Next() bool { + var next bool + if itr.reverse { + currKey, _, ok := SplitMVCCKey(itr.source.Key()) + if !ok { + // XXX: This should not happen as that would indicate we have a malformed + // MVCC key. + panic(fmt.Sprintf("invalid PebbleDB MVCC key: %s", itr.source.Key())) + } + + // Since PebbleDB has no PrevPrefix API, we must manually seek to the next + // key that is lexicographically less than the current key. + next = itr.source.SeekLT(MVCCEncode(currKey, 0)) + } else { + // move the cursor to the next key + next = itr.source.NextPrefix() + } + // First move the iterator to the next prefix, which may not correspond to the // desired version for that key, e.g. if the key was written at a later version, // so we seek back to the latest desired version, s.t. the version is <= itr.version. - if itr.source.NextPrefix() { + if next { nextKey, _, ok := SplitMVCCKey(itr.source.Key()) if !ok { // XXX: This should not happen as that would indicate we have a malformed @@ -237,7 +262,14 @@ func (itr *iterator) DebugRawIterate() { fmt.Printf("KEY: %s, VALUE: %s, VERSION: %d, TOMBSTONE: %d\n", key, val, version, tombstone) - if itr.source.NextPrefix() { + var next bool + if itr.reverse { + next = itr.source.SeekLT(MVCCEncode(key, 0)) + } else { + next = itr.source.NextPrefix() + } + + if next { nextKey, _, ok := SplitMVCCKey(itr.source.Key()) if !ok { panic(fmt.Sprintf("invalid PebbleDB MVCC key: %s", itr.source.Key()))