interface.go raw

   1  package walletdb
   2  
   3  // This interface was inspired heavily by the excellent boltdb project at https://github.com/boltdb/bolt by Ben B.
   4  // Johnson.
   5  import (
   6  	"io"
   7  )
   8  
   9  // ReadTx represents a database transaction that can only be used for reads. If a database update must occur, use a
  10  // ReadWriteTx.
  11  type ReadTx interface {
  12  	// ReadBucket opens the root bucket for read only access. If the bucket described by the key does not exist, nil is
  13  	// returned.
  14  	ReadBucket(key []byte) ReadBucket
  15  	// Rollback closes the transaction, discarding changes (if any) if the database was modified by a write transaction.
  16  	Rollback() error
  17  }
  18  
  19  // ReadWriteTx represents a database transaction that can be used for both reads and writes. When only reads are
  20  // necessary, consider using a ReadTx instead.
  21  type ReadWriteTx interface {
  22  	ReadTx
  23  	// ReadWriteBucket opens the root bucket for read/write access. If the bucket described by the key does not exist,
  24  	// nil is returned.
  25  	ReadWriteBucket(key []byte) ReadWriteBucket
  26  	// CreateTopLevelBucket creates the top level bucket for a key if it does not exist. The newly-created bucket it
  27  	// returned.
  28  	CreateTopLevelBucket(key []byte) (ReadWriteBucket, error)
  29  	// DeleteTopLevelBucket deletes the top level bucket for a key. This errors if the bucket can not be found or the
  30  	// key keys a single value instead of a bucket.
  31  	DeleteTopLevelBucket(key []byte) error
  32  	// Commit commits all changes that have been on the transaction's root buckets and all of their sub-buckets to
  33  	// persistent storage.
  34  	Commit() error
  35  }
  36  
  37  // ReadBucket represents a bucket (a hierarchical structure within the database) that is only allowed to perform read
  38  // operations.
  39  type ReadBucket interface {
  40  	// NestedReadBucket retrieves a nested bucket with the given key. Returns nil if the bucket does not exist.
  41  	NestedReadBucket(key []byte) ReadBucket
  42  	// ForEach invokes the passed function with every key/value pair in the bucket. This includes nested buckets, in
  43  	// which case the value is nil, but it does not include the key/value pairs within those nested buckets.
  44  	//
  45  	// NOTE: The values returned by this function are only valid during a transaction. Attempting to access them after a
  46  	// transaction has ended results in undefined behavior. This constraint prevents additional data copies and allows
  47  	// support for memory-mapped database implementations.
  48  	ForEach(func(k, v []byte) error) error
  49  	// Get returns the value for the given key. Returns nil if the key does not exist in this bucket (or nested
  50  	// buckets).
  51  	//
  52  	// NOTE: The value returned by this function is only valid during a transaction. Attempting to access it after a
  53  	// transaction has ended results in undefined behavior. This constraint prevents additional data copies and allows
  54  	// support for memory-mapped database implementations.
  55  	Get(key []byte) []byte
  56  	ReadCursor() ReadCursor
  57  }
  58  
  59  // ReadWriteBucket represents a bucket (a hierarchical structure within the database) that is allowed to perform both
  60  // read and write operations.
  61  type ReadWriteBucket interface {
  62  	ReadBucket
  63  	// NestedReadWriteBucket retrieves a nested bucket with the given key. Returns nil if the bucket does not exist.
  64  	NestedReadWriteBucket(key []byte) ReadWriteBucket
  65  	// CreateBucket creates and returns a new nested bucket with the given key. Returns ErrBucketExists if the bucket
  66  	// already exists, ErrBucketNameRequired if the key is empty, or ErrIncompatibleValue if the key value is otherwise
  67  	// invalid for the particular database implementation. Other errors are possible depending on the implementation.
  68  	CreateBucket(key []byte) (ReadWriteBucket, error)
  69  	// CreateBucketIfNotExists creates and returns a new nested bucket with the given key if it does not already exist.
  70  	// Returns ErrBucketNameRequired if the key is empty or ErrIncompatibleValue if the key value is otherwise invalid
  71  	// for the particular database backend. Other errors are possible depending on the implementation.
  72  	CreateBucketIfNotExists(key []byte) (ReadWriteBucket, error)
  73  	// DeleteNestedBucket removes a nested bucket with the given key. Returns ErrTxNotWritable if attempted against a
  74  	// read-only transaction and ErrBucketNotFound if the specified bucket does not exist.
  75  	DeleteNestedBucket(key []byte) error
  76  	// Put saves the specified key/value pair to the bucket. Keys that do not already exist are added and keys that
  77  	// already exist are overwritten. Returns ErrTxNotWritable if attempted against a read-only transaction.
  78  	Put(key, value []byte) error
  79  	// Delete removes the specified key from the bucket. Deleting a key that does not exist does not return an error.
  80  	// Returns ErrTxNotWritable if attempted against a read-only transaction.
  81  	Delete(key []byte) error
  82  	
  83  	// ReadWriteCursor returns a new cursor, allowing for iteration over the bucket's key/value pairs and nested buckets
  84  	// in forward or backward order.
  85  	ReadWriteCursor() ReadWriteCursor
  86  }
  87  
  88  // ReadCursor represents a bucket cursor that can be positioned at the start or end of the bucket's key/value pairs and
  89  // iterate over pairs in the bucket. This type is only allowed to perform database read operations.
  90  type ReadCursor interface {
  91  	// First positions the cursor at the first key/value pair and returns the pair.
  92  	First() (key, value []byte)
  93  	// Last positions the cursor at the last key/value pair and returns the
  94  	// pair.
  95  	Last() (key, value []byte)
  96  	// Next moves the cursor one key/value pair forward and returns the new
  97  	// pair.
  98  	Next() (key, value []byte)
  99  	// Prev moves the cursor one key/value pair backward and returns the new
 100  	// pair.
 101  	Prev() (key, value []byte)
 102  	// Seek positions the cursor at the passed seek key. If the key does not exist, the cursor is moved to the next key
 103  	// after seek. Returns the new pair.
 104  	Seek(seek []byte) (key, value []byte)
 105  }
 106  
 107  // ReadWriteCursor represents a bucket cursor that can be positioned at the start or end of the bucket's key/value pairs
 108  // and iterate over pairs in the bucket. This abstraction is allowed to perform both database read and write operations.
 109  type ReadWriteCursor interface {
 110  	ReadCursor
 111  	// Delete removes the current key/value pair the cursor is at without invalidating the cursor. Returns
 112  	// ErrIncompatibleValue if attempted when the cursor points to a nested bucket.
 113  	Delete() error
 114  }
 115  
 116  // BucketIsEmpty returns whether the bucket is empty, that is, whether there are no key/value pairs or nested buckets.
 117  func BucketIsEmpty(bucket ReadBucket) bool {
 118  	k, v := bucket.ReadCursor().First()
 119  	return k == nil && v == nil
 120  }
 121  
 122  // DB represents an ACID database. All database access is performed through read or read+write transactions.
 123  type DB interface {
 124  	// BeginReadTx opens a database read transaction.
 125  	BeginReadTx() (ReadTx, error)
 126  	// BeginReadWriteTx opens a database read+write transaction.
 127  	BeginReadWriteTx() (ReadWriteTx, error)
 128  	// Copy writes a copy of the database to the provided writer. This call will start a read-only transaction to
 129  	// perform all operations.
 130  	Copy(w io.Writer) error
 131  	// Close cleanly shuts down the database and syncs all data.
 132  	Close() error
 133  }
 134  
 135  // View opens a database read transaction and executes the function f with the transaction passed as a parameter. After
 136  // f exits, the transaction is rolled back. If f errors, its error is returned, not a rollback error (if any occur).
 137  func View(db DB, f func(tx ReadTx) error) (e error) {
 138  	var tx ReadTx
 139  	tx, e = db.BeginReadTx()
 140  	if e != nil {
 141  		E.Ln(e)
 142  		return e
 143  	}
 144  	e = f(tx)
 145  	rollbackErr := tx.Rollback()
 146  	if e != nil {
 147  		E.Ln(e)
 148  		return e
 149  	}
 150  	if rollbackErr != nil {
 151  		return rollbackErr
 152  	}
 153  	return nil
 154  }
 155  
 156  // Update opens a database read/write transaction and executes the function f with the transaction passed as a
 157  // parameter.
 158  //
 159  // After f exits, if f did not error, the transaction is committed.
 160  //
 161  // Otherwise, if f did error, the transaction is rolled back.
 162  //
 163  // If the rollback fails, the original error returned by f is still returned.
 164  //
 165  // If the commit fails, the commit error is returned.
 166  func Update(db DB, f func(tx ReadWriteTx) error) (e error) {
 167  	var tx ReadWriteTx
 168  	tx, e = db.BeginReadWriteTx()
 169  	if e != nil {
 170  		E.Ln(e)
 171  		return e
 172  	}
 173  	e = f(tx)
 174  	if e != nil {
 175  		E.Ln(e)
 176  		// Want to return the original error, not a rollback error if any occur.
 177  		_ = tx.Rollback()
 178  		return e
 179  	}
 180  	return tx.Commit()
 181  }
 182  
 183  // Driver defines a structure for backend drivers to use when they registered themselves as a backend which implements
 184  // the Db interface.
 185  type Driver struct {
 186  	// DbType is the identifier used to uniquely identify a specific database driver. There can be only one driver with
 187  	// the same name.
 188  	DbType string
 189  	// Create is the function that will be invoked with all user-specified arguments to create the database. This
 190  	// function must return ErrDbExists if the database already exists.
 191  	Create func(args ...interface{}) (DB, error)
 192  	// Open is the function that will be invoked with all user-specified arguments to open the database. This function
 193  	// must return ErrDbDoesNotExist if the database has not already been created.
 194  	Open func(args ...interface{}) (DB, error)
 195  }
 196  
 197  // driverList holds all of the registered database backends.
 198  var drivers = make(map[string]*Driver)
 199  
 200  // RegisterDriver adds a backend database driver to available interfaces. ErrDbTypeRegistered will be retruned if the
 201  // database type for the driver has already been registered.
 202  func RegisterDriver(driver Driver) (e error) {
 203  	if _, exists := drivers[driver.DbType]; exists {
 204  		return ErrDbTypeRegistered
 205  	}
 206  	drivers[driver.DbType] = &driver
 207  	return nil
 208  }
 209  
 210  // SupportedDrivers returns a slice of strings that represent the database drivers that have been registered and are
 211  // therefore supported.
 212  func SupportedDrivers() []string {
 213  	supportedDBs := make([]string, 0, len(drivers))
 214  	for _, drv := range drivers {
 215  		supportedDBs = append(supportedDBs, drv.DbType)
 216  	}
 217  	return supportedDBs
 218  }
 219  
 220  // Create intializes and opens a database for the specified type. The arguments are specific to the database type
 221  // driver. See the documentation for the database driver for further details.
 222  //
 223  // ErrDbUnknownType will be returned if the the database type is not registered.
 224  func Create(dbType string, args ...interface{}) (DB, error) {
 225  	drv, exists := drivers[dbType]
 226  	if !exists {
 227  		return nil, ErrDbUnknownType
 228  	}
 229  	return drv.Create(args...)
 230  }
 231  
 232  // Open opens an existing database for the specified type. The arguments are specific to the database type driver. See
 233  // the documentation for the database driver for further details.
 234  //
 235  // ErrDbUnknownType will be returned if the the database type is not registered.
 236  func Open(dbType string, args ...interface{}) (DB, error) {
 237  	drv, exists := drivers[dbType]
 238  	if !exists {
 239  		return nil, ErrDbUnknownType
 240  	}
 241  	return drv.Open(args...)
 242  }
 243