lbcwallet/walletdb/interface.go
Oliver Gugger a27bab6eb9
walletdb: add View and Update to DB interface
We want to make sure that every database backend has their own
implementation of the View() and Update() methods to make sure any
custom retry/commit/rollback strategies are used even when invoked
through the package-provided View() and Update() functions.
2021-07-29 16:53:09 +02:00

362 lines
14 KiB
Go

// Copyright (c) 2014 The btcsuite developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
// This interface was inspired heavily by the excellent boltdb project at
// https://github.com/boltdb/bolt by Ben B. Johnson.
package walletdb
import (
"fmt"
"io"
)
// ReadTx represents a database transaction that can only be used for reads. If
// a database update must occur, use a ReadWriteTx.
type ReadTx interface {
// ReadBucket opens the root bucket for read only access. If the bucket
// described by the key does not exist, nil is returned.
ReadBucket(key []byte) ReadBucket
// ForEachBucket will iterate through all top level buckets.
ForEachBucket(func(key []byte) error) error
// Rollback closes the transaction, discarding changes (if any) if the
// database was modified by a write transaction.
Rollback() error
}
// ReadWriteTx represents a database transaction that can be used for both reads
// and writes. When only reads are necessary, consider using a ReadTx instead.
type ReadWriteTx interface {
ReadTx
// ReadWriteBucket opens the root bucket for read/write access. If the
// bucket described by the key does not exist, nil is returned.
ReadWriteBucket(key []byte) ReadWriteBucket
// CreateTopLevelBucket creates the top level bucket for a key if it
// does not exist. The newly-created bucket it returned.
CreateTopLevelBucket(key []byte) (ReadWriteBucket, error)
// DeleteTopLevelBucket deletes the top level bucket for a key. This
// errors if the bucket can not be found or the key keys a single value
// instead of a bucket.
DeleteTopLevelBucket(key []byte) error
// Commit commits all changes that have been on the transaction's root
// buckets and all of their sub-buckets to persistent storage.
Commit() error
// OnCommit takes a function closure that will be executed when the
// transaction successfully gets committed.
OnCommit(func())
}
// ReadBucket represents a bucket (a hierarchical structure within the database)
// that is only allowed to perform read operations.
type ReadBucket interface {
// NestedReadBucket retrieves a nested bucket with the given key.
// Returns nil if the bucket does not exist.
NestedReadBucket(key []byte) ReadBucket
// ForEach invokes the passed function with every key/value pair in
// the bucket. This includes nested buckets, in which case the value
// is nil, but it does not include the key/value pairs within those
// nested buckets.
//
// NOTE: The values returned by this function are only valid during a
// transaction. Attempting to access them after a transaction has ended
// results in undefined behavior. This constraint prevents additional
// data copies and allows support for memory-mapped database
// implementations.
ForEach(func(k, v []byte) error) error
// Get returns the value for the given key. Returns nil if the key does
// not exist in this bucket (or nested buckets).
//
// NOTE: The value returned by this function is only valid during a
// transaction. Attempting to access it after a transaction has ended
// results in undefined behavior. This constraint prevents additional
// data copies and allows support for memory-mapped database
// implementations.
Get(key []byte) []byte
ReadCursor() ReadCursor
}
// ReadWriteBucket represents a bucket (a hierarchical structure within the
// database) that is allowed to perform both read and write operations.
type ReadWriteBucket interface {
ReadBucket
// NestedReadWriteBucket retrieves a nested bucket with the given key.
// Returns nil if the bucket does not exist.
NestedReadWriteBucket(key []byte) ReadWriteBucket
// CreateBucket creates and returns a new nested bucket with the given
// key. Returns ErrBucketExists if the bucket already exists,
// ErrBucketNameRequired if the key is empty, or ErrIncompatibleValue
// if the key value is otherwise invalid for the particular database
// implementation. Other errors are possible depending on the
// implementation.
CreateBucket(key []byte) (ReadWriteBucket, error)
// CreateBucketIfNotExists creates and returns a new nested bucket with
// the given key if it does not already exist. Returns
// ErrBucketNameRequired if the key is empty or ErrIncompatibleValue
// if the key value is otherwise invalid for the particular database
// backend. Other errors are possible depending on the implementation.
CreateBucketIfNotExists(key []byte) (ReadWriteBucket, error)
// DeleteNestedBucket removes a nested bucket with the given key.
// Returns ErrTxNotWritable if attempted against a read-only transaction
// and ErrBucketNotFound if the specified bucket does not exist.
DeleteNestedBucket(key []byte) error
// Put saves the specified key/value pair to the bucket. Keys that do
// not already exist are added and keys that already exist are
// overwritten. Returns ErrTxNotWritable if attempted against a
// read-only transaction.
Put(key, value []byte) error
// Delete removes the specified key from the bucket. Deleting a key
// that does not exist does not return an error. Returns
// ErrTxNotWritable if attempted against a read-only transaction.
Delete(key []byte) error
// ReadWriteCursor returns a new cursor, allowing for iteration over the
// bucket's key/value pairs and nested buckets in forward or backward
// order.
ReadWriteCursor() ReadWriteCursor
// Tx returns the bucket's transaction.
Tx() ReadWriteTx
// NextSequence returns an autoincrementing integer for the bucket.
NextSequence() (uint64, error)
// SetSequence updates the sequence number for the bucket.
SetSequence(v uint64) error
// Sequence returns the current integer for the bucket without
// incrementing it.
Sequence() uint64
}
// ReadCursor represents a bucket cursor that can be positioned at the start or
// end of the bucket's key/value pairs and iterate over pairs in the bucket.
// This type is only allowed to perform database read operations.
type ReadCursor interface {
// First positions the cursor at the first key/value pair and returns
// the pair.
First() (key, value []byte)
// Last positions the cursor at the last key/value pair and returns the
// pair.
Last() (key, value []byte)
// Next moves the cursor one key/value pair forward and returns the new
// pair.
Next() (key, value []byte)
// Prev moves the cursor one key/value pair backward and returns the new
// pair.
Prev() (key, value []byte)
// 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.
Seek(seek []byte) (key, value []byte)
}
// ReadWriteCursor represents a bucket cursor that can be positioned at the
// start or end of the bucket's key/value pairs and iterate over pairs in the
// bucket. This abstraction is allowed to perform both database read and write
// operations.
type ReadWriteCursor interface {
ReadCursor
// 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.
Delete() error
}
// BucketIsEmpty returns whether the bucket is empty, that is, whether there are
// no key/value pairs or nested buckets.
func BucketIsEmpty(bucket ReadBucket) bool {
k, v := bucket.ReadCursor().First()
return k == nil && v == nil
}
// DB represents an ACID database. All database access is performed through
// read or read+write transactions.
type DB interface {
// BeginReadTx opens a database read transaction.
BeginReadTx() (ReadTx, error)
// BeginReadWriteTx opens a database read+write transaction.
BeginReadWriteTx() (ReadWriteTx, error)
// Copy writes a copy of the database to the provided writer. This
// call will start a read-only transaction to perform all operations.
Copy(w io.Writer) error
// Close cleanly shuts down the database and syncs all data.
Close() error
// PrintStats returns all collected stats pretty printed into a string.
PrintStats() string
// View opens a database read transaction and executes the function f
// with the transaction passed as a parameter. After f exits, the
// transaction is rolled back. If f errors, its error is returned, not a
// rollback error (if any occur). The passed reset function is called
// before the start of the transaction and can be used to reset
// intermediate state. As callers may expect retries of the f closure
// (depending on the database backend used), the reset function will be
// called before each retry respectively.
//
// NOTE: For new code, this method should be used directly instead of
// the package level View() function.
View(f func(tx ReadTx) error, reset func()) error
// Update opens a database read/write transaction and executes the
// function f with the transaction passed as a parameter. After f exits,
// if f did not error, the transaction is committed. Otherwise, if f did
// error, the transaction is rolled back. If the rollback fails, the
// original error returned by f is still returned. If the commit fails,
// the commit error is returned. As callers may expect retries of the f
// closure (depending on the database backend used), the reset function
// will be called before each retry respectively.
//
// NOTE: For new code, this method should be used directly instead of
// the package level Update() function.
Update(f func(tx ReadWriteTx) error, reset func()) error
}
// BatchDB is a special version of the main DB interface that allows the caller
// to specify write transactions that should be combined toegether if multiple
// goroutines are calling the Batch method.
type BatchDB interface {
DB
// Batch is similar to the package-level Update method, but it will
// attempt to optimistically combine the invocation of several
// transaction functions into a single db write transaction.
Batch(func(tx ReadWriteTx) error) error
}
// View opens a database read transaction and executes the function f with the
// transaction passed as a parameter. After f exits, the transaction is rolled
// back. If f errors, its error is returned, not a rollback error (if any
// occur).
//
// NOTE: For new code the database backend's View method should be used directly
// as this package level function will be phased out in the future.
func View(db DB, f func(tx ReadTx) error) error {
return db.View(f, func() {})
}
// Update opens a database read/write transaction and executes the function f
// with the transaction passed as a parameter. After f exits, if f did not
// error, the transaction is committed. Otherwise, if f did error, the
// transaction is rolled back. If the rollback fails, the original error
// returned by f is still returned. If the commit fails, the commit error is
// returned.
//
// NOTE: For new code the database backend's Update method should be used
// directly as this package level function will be phased out in the future.
func Update(db DB, f func(tx ReadWriteTx) error) error {
return db.Update(f, func() {})
}
// Batch opens a database read/write transaction and executes the function f
// with the transaction passed as a parameter. After f exits, if f did not
// error, the transaction is committed. Otherwise, if f did error, the
// transaction is rolled back. If the rollback fails, the original error
// returned by f is still returned. If the commit fails, the commit error is
// returned.
//
// Batch is only useful when there are multiple goroutines calling it.
func Batch(db DB, f func(tx ReadWriteTx) error) error {
batchDB, ok := db.(BatchDB)
if !ok {
return fmt.Errorf("need batch")
}
return batchDB.Batch(f)
}
// Driver defines a structure for backend drivers to use when they registered
// themselves as a backend which implements the Db interface.
type Driver struct {
// DbType is the identifier used to uniquely identify a specific
// database driver. There can be only one driver with the same name.
DbType string
// Create is the function that will be invoked with all user-specified
// arguments to create the database. This function must return
// ErrDbExists if the database already exists.
Create func(args ...interface{}) (DB, error)
// Open is the function that will be invoked with all user-specified
// arguments to open the database. This function must return
// ErrDbDoesNotExist if the database has not already been created.
Open func(args ...interface{}) (DB, error)
}
// driverList holds all of the registered database backends.
var drivers = make(map[string]*Driver)
// RegisterDriver adds a backend database driver to available interfaces.
// ErrDbTypeRegistered will be retruned if the database type for the driver has
// already been registered.
func RegisterDriver(driver Driver) error {
if _, exists := drivers[driver.DbType]; exists {
return ErrDbTypeRegistered
}
drivers[driver.DbType] = &driver
return nil
}
// SupportedDrivers returns a slice of strings that represent the database
// drivers that have been registered and are therefore supported.
func SupportedDrivers() []string {
supportedDBs := make([]string, 0, len(drivers))
for _, drv := range drivers {
supportedDBs = append(supportedDBs, drv.DbType)
}
return supportedDBs
}
// Create intializes and opens a database for the specified type. The arguments
// are specific to the database type driver. See the documentation for the
// database driver for further details.
//
// ErrDbUnknownType will be returned if the the database type is not registered.
func Create(dbType string, args ...interface{}) (DB, error) {
drv, exists := drivers[dbType]
if !exists {
return nil, ErrDbUnknownType
}
return drv.Create(args...)
}
// Open opens an existing database for the specified type. The arguments are
// specific to the database type driver. See the documentation for the database
// driver for further details.
//
// ErrDbUnknownType will be returned if the the database type is not registered.
func Open(dbType string, args ...interface{}) (DB, error) {
drv, exists := drivers[dbType]
if !exists {
return nil, ErrDbUnknownType
}
return drv.Open(args...)
}