lnd.xprv/channeldb/kvdb/etcd/readwrite_cursor.go
Andras Banki-Horvath 63e9d6102f
kvdb+etcd: change flattened bucket key derivation algorithm
This commit changes the key derivation algo we use to emulate buckets
similar to bbolt. The issue with prefixing keys with either a bucket or
a value prefix is that the cursor couldn't effectively iterate trough
all keys in a bucket, as it skipped the bucket keys.
While there are multiple ways to fix that issue (eg. two pointers,
iterating value keys then bucket keys, etc), the cleanest is to instead
of prefixes in keys we use a postfix indicating whether a key is a
bucket or a value. This also simplifies all operations where we
(recursively) iterate a bucket and is equivalent with the prefixing key
derivation with the addition that bucket and value keys are now
continous.
2020-07-28 17:57:29 +02:00

144 lines
3.4 KiB
Go

// +build kvdb_etcd
package etcd
// readWriteCursor holds a reference to the cursors bucket, the value
// prefix and the current key used while iterating.
type readWriteCursor struct {
// bucket holds the reference to the parent bucket.
bucket *readWriteBucket
// prefix holds the value prefix which is in front of each
// value key in the bucket.
prefix string
// currKey holds the current key of the cursor.
currKey string
}
func newReadWriteCursor(bucket *readWriteBucket) *readWriteCursor {
return &readWriteCursor{
bucket: bucket,
prefix: string(bucket.id),
}
}
// First positions the cursor at the first key/value pair and returns
// the pair.
func (c *readWriteCursor) First() (key, value []byte) {
// Get the first key with the value prefix.
kv, err := c.bucket.tx.stm.First(c.prefix)
if err != nil {
// TODO: revise this once kvdb interface supports errors
return nil, nil
}
if kv != nil {
c.currKey = kv.key
return getKeyVal(kv)
}
return nil, nil
}
// Last positions the cursor at the last key/value pair and returns the
// pair.
func (c *readWriteCursor) Last() (key, value []byte) {
kv, err := c.bucket.tx.stm.Last(c.prefix)
if err != nil {
// TODO: revise this once kvdb interface supports errors
return nil, nil
}
if kv != nil {
c.currKey = kv.key
return getKeyVal(kv)
}
return nil, nil
}
// Next moves the cursor one key/value pair forward and returns the new
// pair.
func (c *readWriteCursor) Next() (key, value []byte) {
kv, err := c.bucket.tx.stm.Next(c.prefix, c.currKey)
if err != nil {
// TODO: revise this once kvdb interface supports errors
return nil, nil
}
if kv != nil {
c.currKey = kv.key
return getKeyVal(kv)
}
return nil, nil
}
// Prev moves the cursor one key/value pair backward and returns the new
// pair.
func (c *readWriteCursor) Prev() (key, value []byte) {
kv, err := c.bucket.tx.stm.Prev(c.prefix, c.currKey)
if err != nil {
// TODO: revise this once kvdb interface supports errors
return nil, nil
}
if kv != nil {
c.currKey = kv.key
return getKeyVal(kv)
}
return nil, nil
}
// Seek positions the cursor at the passed seek key. If the key does
// not exist, the cursor is moved to the next key after seek. Returns
// the new pair.
func (c *readWriteCursor) Seek(seek []byte) (key, value []byte) {
// Return nil if trying to seek to an empty key.
if seek == nil {
return nil, nil
}
// Seek to the first key with prefix + seek. If that key is not present
// STM will seek to the next matching key with prefix.
kv, err := c.bucket.tx.stm.Seek(c.prefix, c.prefix+string(seek))
if err != nil {
// TODO: revise this once kvdb interface supports errors
return nil, nil
}
if kv != nil {
c.currKey = kv.key
return getKeyVal(kv)
}
return nil, nil
}
// Delete removes the current key/value pair the cursor is at without
// invalidating the cursor. Returns ErrIncompatibleValue if attempted
// when the cursor points to a nested bucket.
func (c *readWriteCursor) Delete() error {
// Get the next key after the current one. We could do this
// after deletion too but it's one step more efficient here.
nextKey, err := c.bucket.tx.stm.Next(c.prefix, c.currKey)
if err != nil {
return err
}
if isBucketKey(c.currKey) {
c.bucket.DeleteNestedBucket(getKey(c.currKey))
} else {
c.bucket.Delete(getKey(c.currKey))
}
if nextKey != nil {
// Set current key to the next one.
c.currKey = nextKey.key
}
return nil
}