2022-12-15 17:13:13 +01:00
|
|
|
//go:build kvdb_postgres || (kvdb_sqlite && !(windows && (arm || 386)) && !(linux && (ppc64 || mips || mipsle || mips64)))
|
2021-07-13 12:07:48 +02:00
|
|
|
|
2023-01-17 12:54:12 +01:00
|
|
|
package sqlbase
|
2021-07-13 12:07:48 +02:00
|
|
|
|
|
|
|
import (
|
|
|
|
"database/sql"
|
|
|
|
|
|
|
|
"github.com/btcsuite/btcwallet/walletdb"
|
|
|
|
)
|
|
|
|
|
|
|
|
// readWriteCursor holds a reference to the cursors bucket, the value
|
|
|
|
// prefix and the current key used while iterating.
|
|
|
|
type readWriteCursor struct {
|
|
|
|
bucket *readWriteBucket
|
|
|
|
|
|
|
|
// currKey holds the current key of the cursor.
|
|
|
|
currKey []byte
|
|
|
|
}
|
|
|
|
|
|
|
|
func newReadWriteCursor(b *readWriteBucket) *readWriteCursor {
|
|
|
|
return &readWriteCursor{
|
|
|
|
bucket: b,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// First positions the cursor at the first key/value pair and returns
|
|
|
|
// the pair.
|
|
|
|
func (c *readWriteCursor) First() ([]byte, []byte) {
|
|
|
|
var (
|
|
|
|
key []byte
|
|
|
|
value []byte
|
|
|
|
)
|
2021-12-22 12:33:50 +01:00
|
|
|
row, cancel := c.bucket.tx.QueryRow(
|
|
|
|
"SELECT key, value FROM " + c.bucket.table + " WHERE " +
|
|
|
|
parentSelector(c.bucket.id) +
|
2021-07-13 12:07:48 +02:00
|
|
|
" ORDER BY key LIMIT 1",
|
2021-12-22 12:33:50 +01:00
|
|
|
)
|
|
|
|
defer cancel()
|
|
|
|
err := row.Scan(&key, &value)
|
2021-07-13 12:07:48 +02:00
|
|
|
|
|
|
|
switch {
|
|
|
|
case err == sql.ErrNoRows:
|
|
|
|
return nil, nil
|
|
|
|
|
|
|
|
case err != nil:
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Copy current key to prevent modification by the caller.
|
|
|
|
c.currKey = make([]byte, len(key))
|
|
|
|
copy(c.currKey, key)
|
|
|
|
|
|
|
|
return key, value
|
|
|
|
}
|
|
|
|
|
|
|
|
// Last positions the cursor at the last key/value pair and returns the
|
|
|
|
// pair.
|
|
|
|
func (c *readWriteCursor) Last() ([]byte, []byte) {
|
|
|
|
var (
|
|
|
|
key []byte
|
|
|
|
value []byte
|
|
|
|
)
|
2021-12-22 12:33:50 +01:00
|
|
|
row, cancel := c.bucket.tx.QueryRow(
|
|
|
|
"SELECT key, value FROM " + c.bucket.table + " WHERE " +
|
|
|
|
parentSelector(c.bucket.id) +
|
2021-07-13 12:07:48 +02:00
|
|
|
" ORDER BY key DESC LIMIT 1",
|
2021-12-22 12:33:50 +01:00
|
|
|
)
|
|
|
|
defer cancel()
|
|
|
|
err := row.Scan(&key, &value)
|
2021-07-13 12:07:48 +02:00
|
|
|
|
|
|
|
switch {
|
|
|
|
case err == sql.ErrNoRows:
|
|
|
|
return nil, nil
|
|
|
|
|
|
|
|
case err != nil:
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Copy current key to prevent modification by the caller.
|
|
|
|
c.currKey = make([]byte, len(key))
|
|
|
|
copy(c.currKey, key)
|
|
|
|
|
|
|
|
return key, value
|
|
|
|
}
|
|
|
|
|
|
|
|
// Next moves the cursor one key/value pair forward and returns the new
|
|
|
|
// pair.
|
|
|
|
func (c *readWriteCursor) Next() ([]byte, []byte) {
|
|
|
|
var (
|
|
|
|
key []byte
|
|
|
|
value []byte
|
|
|
|
)
|
2021-12-22 12:33:50 +01:00
|
|
|
row, cancel := c.bucket.tx.QueryRow(
|
2021-07-13 12:07:48 +02:00
|
|
|
"SELECT key, value FROM "+c.bucket.table+" WHERE "+
|
|
|
|
parentSelector(c.bucket.id)+
|
|
|
|
" AND key>$1 ORDER BY key LIMIT 1",
|
|
|
|
c.currKey,
|
2021-12-22 12:33:50 +01:00
|
|
|
)
|
|
|
|
defer cancel()
|
|
|
|
err := row.Scan(&key, &value)
|
2021-07-13 12:07:48 +02:00
|
|
|
|
|
|
|
switch {
|
|
|
|
case err == sql.ErrNoRows:
|
|
|
|
return nil, nil
|
|
|
|
|
|
|
|
case err != nil:
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Copy current key to prevent modification by the caller.
|
|
|
|
c.currKey = make([]byte, len(key))
|
|
|
|
copy(c.currKey, key)
|
|
|
|
|
|
|
|
return key, value
|
|
|
|
}
|
|
|
|
|
|
|
|
// Prev moves the cursor one key/value pair backward and returns the new
|
|
|
|
// pair.
|
|
|
|
func (c *readWriteCursor) Prev() ([]byte, []byte) {
|
|
|
|
var (
|
|
|
|
key []byte
|
|
|
|
value []byte
|
|
|
|
)
|
2021-12-22 12:33:50 +01:00
|
|
|
row, cancel := c.bucket.tx.QueryRow(
|
2021-07-13 12:07:48 +02:00
|
|
|
"SELECT key, value FROM "+c.bucket.table+" WHERE "+
|
|
|
|
parentSelector(c.bucket.id)+
|
|
|
|
" AND key<$1 ORDER BY key DESC LIMIT 1",
|
|
|
|
c.currKey,
|
2021-12-22 12:33:50 +01:00
|
|
|
)
|
|
|
|
defer cancel()
|
|
|
|
err := row.Scan(&key, &value)
|
2021-07-13 12:07:48 +02:00
|
|
|
|
|
|
|
switch {
|
|
|
|
case err == sql.ErrNoRows:
|
|
|
|
return nil, nil
|
|
|
|
|
|
|
|
case err != nil:
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Copy current key to prevent modification by the caller.
|
|
|
|
c.currKey = make([]byte, len(key))
|
|
|
|
copy(c.currKey, key)
|
|
|
|
|
|
|
|
return key, value
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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) ([]byte, []byte) {
|
|
|
|
// Convert nil to empty slice, otherwise sql mapping won't be correct
|
|
|
|
// and no keys are found.
|
|
|
|
if seek == nil {
|
|
|
|
seek = []byte{}
|
|
|
|
}
|
|
|
|
|
|
|
|
var (
|
|
|
|
key []byte
|
|
|
|
value []byte
|
|
|
|
)
|
2021-12-22 12:33:50 +01:00
|
|
|
row, cancel := c.bucket.tx.QueryRow(
|
2021-07-13 12:07:48 +02:00
|
|
|
"SELECT key, value FROM "+c.bucket.table+" WHERE "+
|
|
|
|
parentSelector(c.bucket.id)+
|
|
|
|
" AND key>=$1 ORDER BY key LIMIT 1",
|
|
|
|
seek,
|
2021-12-22 12:33:50 +01:00
|
|
|
)
|
|
|
|
defer cancel()
|
|
|
|
err := row.Scan(&key, &value)
|
2021-07-13 12:07:48 +02:00
|
|
|
|
|
|
|
switch {
|
|
|
|
case err == sql.ErrNoRows:
|
|
|
|
return nil, nil
|
|
|
|
|
|
|
|
case err != nil:
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Copy current key to prevent modification by the caller.
|
|
|
|
c.currKey = make([]byte, len(key))
|
|
|
|
copy(c.currKey, key)
|
|
|
|
|
|
|
|
return key, value
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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 first record at or after cursor.
|
|
|
|
var key []byte
|
2021-12-22 12:33:50 +01:00
|
|
|
row, cancel := c.bucket.tx.QueryRow(
|
2021-07-13 12:07:48 +02:00
|
|
|
"SELECT key FROM "+c.bucket.table+" WHERE "+
|
|
|
|
parentSelector(c.bucket.id)+
|
|
|
|
" AND key>=$1 ORDER BY key LIMIT 1",
|
|
|
|
c.currKey,
|
2021-12-22 12:33:50 +01:00
|
|
|
)
|
|
|
|
defer cancel()
|
|
|
|
err := row.Scan(&key)
|
2021-07-13 12:07:48 +02:00
|
|
|
|
|
|
|
switch {
|
|
|
|
case err == sql.ErrNoRows:
|
|
|
|
return nil
|
|
|
|
|
|
|
|
case err != nil:
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Delete record.
|
|
|
|
result, err := c.bucket.tx.Exec(
|
|
|
|
"DELETE FROM "+c.bucket.table+" WHERE "+
|
|
|
|
parentSelector(c.bucket.id)+
|
|
|
|
" AND key=$1 AND value IS NOT NULL",
|
|
|
|
key,
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
rows, err := result.RowsAffected()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// The key exists but nothing has been deleted. This means that the key
|
|
|
|
// must have been a bucket key.
|
|
|
|
if rows != 1 {
|
|
|
|
return walletdb.ErrIncompatibleValue
|
|
|
|
}
|
|
|
|
|
|
|
|
return err
|
|
|
|
}
|