Skip to content

Commit 9ecb9d0

Browse files
committed
kvdb/sqlbase: optimize Delete for the rw cursor
Similar to the write bucket, we can optimize for the case where the key already exists, allowing us to skip the extra `SELECT` statement.
1 parent a271eaf commit 9ecb9d0

File tree

1 file changed

+39
-26
lines changed

1 file changed

+39
-26
lines changed

kvdb/sqlbase/readwrite_cursor.go

+39-26
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ package sqlbase
44

55
import (
66
"database/sql"
7+
"errors"
8+
"fmt"
79

810
"github.com/btcsuite/btcwallet/walletdb"
911
)
@@ -184,49 +186,60 @@ func (c *readWriteCursor) Seek(seek []byte) ([]byte, []byte) {
184186
}
185187

186188
// Delete removes the current key/value pair the cursor is at without
187-
// invalidating the cursor. Returns ErrIncompatibleValue if attempted
188-
// when the cursor points to a nested bucket.
189+
// invalidating the cursor. Returns ErrIncompatibleValue if attempted when the
190+
// cursor points to a nested bucket.
189191
func (c *readWriteCursor) Delete() error {
190-
// Get first record at or after cursor.
191-
var key []byte
192-
row, cancel := c.bucket.tx.QueryRow(
193-
"SELECT key FROM "+c.bucket.table+" WHERE "+
194-
parentSelector(c.bucket.id)+
195-
" AND key>=$1 ORDER BY key LIMIT 1",
196-
c.currKey,
197-
)
198-
defer cancel()
199-
err := row.Scan(&key)
200-
201-
switch {
202-
case err == sql.ErrNoRows:
203-
return nil
204-
205-
case err != nil:
206-
panic(err)
192+
if c.currKey == nil {
193+
// Cursor might not be positioned on a valid key.
194+
return errors.New("cursor not positioned on a key")
207195
}
208196

209-
// Delete record.
197+
// Attempt to delete the key the cursor is pointing at, but only if it's
198+
// a value.
210199
result, err := c.bucket.tx.Exec(
211200
"DELETE FROM "+c.bucket.table+" WHERE "+
212201
parentSelector(c.bucket.id)+
213202
" AND key=$1 AND value IS NOT NULL",
214-
key,
203+
c.currKey,
215204
)
216205
if err != nil {
217206
panic(err)
218207
}
219208

220209
rows, err := result.RowsAffected()
221210
if err != nil {
222-
return err
211+
return fmt.Errorf("error getting rows affected "+
212+
"for cursor key %x: %w", c.currKey, err)
213+
}
214+
215+
// If deletion succeeded, we are done.
216+
if rows == 1 {
217+
return nil
223218
}
224219

225-
// The key exists but nothing has been deleted. This means that the key
226-
// must have been a bucket key.
227-
if rows != 1 {
220+
// If rows == 0, the key either didn't exist anymore (concurrent
221+
// delete?) or it was a bucket. Check if it's a bucket.
222+
var existsAsBucket int
223+
rowCheck, cancelCheck := c.bucket.tx.QueryRow(
224+
"SELECT 1 FROM "+c.bucket.table+" WHERE "+
225+
parentSelector(c.bucket.id)+
226+
" AND key=$1 AND value IS NULL", c.currKey,
227+
)
228+
defer cancelCheck()
229+
230+
errCheck := rowCheck.Scan(&existsAsBucket)
231+
if errCheck == nil {
232+
// Found it, and value IS NULL -> It's a bucket.
228233
return walletdb.ErrIncompatibleValue
229234
}
230235

231-
return err
236+
if errCheck == sql.ErrNoRows {
237+
return nil
238+
}
239+
240+
// Some other error during the check.
241+
//
242+
// TODO(roasbeef): panic here like above/
243+
return fmt.Errorf("error checking if cursor key %x is "+
244+
"bucket: %w", c.currKey, errCheck)
232245
}

0 commit comments

Comments
 (0)