Implement new namespaced db package named walletdb.
This commit implements a new namespaced db package which is intended to be used be wallet and any sub-packages as its data storage mechanism. - Key/value store - Namespace support - Allows multiple packages to have their own area in the database without worrying about conflicts - Read-only and read-write transactions with both manual and managed modes - Nested buckets - Supports registration of backend databases - Comprehensive test coverage
This commit is contained in:
parent
9225863706
commit
e8b4de9379
6 changed files with 929 additions and 0 deletions
372
walletdb/bdb/db.go
Normal file
372
walletdb/bdb/db.go
Normal file
|
@ -0,0 +1,372 @@
|
|||
/*
|
||||
* Copyright (c) 2014 Conformal Systems LLC <info@conformal.com>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
package bdb
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/conformal/bolt"
|
||||
"github.com/conformal/btcwallet/walletdb"
|
||||
)
|
||||
|
||||
// convertErr converts some bolt errors to the equivalent walletdb error.
|
||||
func convertErr(err error) error {
|
||||
switch err {
|
||||
// Database open/create errors.
|
||||
case bolt.ErrDatabaseNotOpen:
|
||||
return walletdb.ErrDbNotOpen
|
||||
case bolt.ErrInvalid:
|
||||
return walletdb.ErrInvalid
|
||||
|
||||
// Transaction errors.
|
||||
case bolt.ErrTxNotWritable:
|
||||
return walletdb.ErrTxNotWritable
|
||||
case bolt.ErrTxClosed:
|
||||
return walletdb.ErrTxClosed
|
||||
|
||||
// Value/bucket errors.
|
||||
case bolt.ErrBucketNotFound:
|
||||
return walletdb.ErrBucketNotFound
|
||||
case bolt.ErrBucketExists:
|
||||
return walletdb.ErrBucketExists
|
||||
case bolt.ErrBucketNameRequired:
|
||||
return walletdb.ErrBucketNameRequired
|
||||
case bolt.ErrKeyRequired:
|
||||
return walletdb.ErrKeyRequired
|
||||
case bolt.ErrKeyTooLarge:
|
||||
return walletdb.ErrKeyTooLarge
|
||||
case bolt.ErrValueTooLarge:
|
||||
return walletdb.ErrValueTooLarge
|
||||
case bolt.ErrIncompatibleValue:
|
||||
return walletdb.ErrIncompatibleValue
|
||||
}
|
||||
|
||||
// Return the original error if none of the above applies.
|
||||
return err
|
||||
}
|
||||
|
||||
// bucket is an internal type used to represent a collection of key/value pairs
|
||||
// and implements the walletdb.Bucket interface.
|
||||
type bucket bolt.Bucket
|
||||
|
||||
// Enforce bucket implements the walletdb.Bucket interface.
|
||||
var _ walletdb.Bucket = (*bucket)(nil)
|
||||
|
||||
// Bucket retrieves a nested bucket with the given key. Returns nil if
|
||||
// the bucket does not exist.
|
||||
//
|
||||
// This function is part of the walletdb.Bucket interface implementation.
|
||||
func (b *bucket) Bucket(key []byte) walletdb.Bucket {
|
||||
// This nil check is intentional so the return value can be checked
|
||||
// against nil directly.
|
||||
boltBucket := (*bolt.Bucket)(b).Bucket(key)
|
||||
if boltBucket == nil {
|
||||
return nil
|
||||
}
|
||||
return (*bucket)(boltBucket)
|
||||
}
|
||||
|
||||
// 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.
|
||||
//
|
||||
// This function is part of the walletdb.Bucket interface implementation.
|
||||
func (b *bucket) CreateBucket(key []byte) (walletdb.Bucket, error) {
|
||||
boltBucket, err := (*bolt.Bucket)(b).CreateBucket(key)
|
||||
if err != nil {
|
||||
return nil, convertErr(err)
|
||||
}
|
||||
return (*bucket)(boltBucket), nil
|
||||
}
|
||||
|
||||
// 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.
|
||||
//
|
||||
// This function is part of the walletdb.Bucket interface implementation.
|
||||
func (b *bucket) CreateBucketIfNotExists(key []byte) (walletdb.Bucket, error) {
|
||||
boltBucket, err := (*bolt.Bucket)(b).CreateBucketIfNotExists(key)
|
||||
if err != nil {
|
||||
return nil, convertErr(err)
|
||||
}
|
||||
return (*bucket)(boltBucket), nil
|
||||
}
|
||||
|
||||
// DeleteBucket 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.
|
||||
//
|
||||
// This function is part of the walletdb.Bucket interface implementation.
|
||||
func (b *bucket) DeleteBucket(key []byte) error {
|
||||
return convertErr((*bolt.Bucket)(b).DeleteBucket(key))
|
||||
}
|
||||
|
||||
// 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 will
|
||||
// likely result in an access violation.
|
||||
//
|
||||
// This function is part of the walletdb.Bucket interface implementation.
|
||||
func (b *bucket) ForEach(fn func(k, v []byte) error) error {
|
||||
return convertErr((*bolt.Bucket)(b).ForEach(fn))
|
||||
}
|
||||
|
||||
// Writable returns whether or not the bucket is writable.
|
||||
//
|
||||
// This function is part of the walletdb.Bucket interface implementation.
|
||||
func (b *bucket) Writable() bool {
|
||||
return (*bolt.Bucket)(b).Writable()
|
||||
}
|
||||
|
||||
// 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.
|
||||
//
|
||||
// This function is part of the walletdb.Bucket interface implementation.
|
||||
func (b *bucket) Put(key, value []byte) error {
|
||||
return convertErr((*bolt.Bucket)(b).Put(key, value))
|
||||
}
|
||||
|
||||
// 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
|
||||
// will likely result in an access violation.
|
||||
//
|
||||
// This function is part of the walletdb.Bucket interface implementation.
|
||||
func (b *bucket) Get(key []byte) []byte {
|
||||
return (*bolt.Bucket)(b).Get(key)
|
||||
}
|
||||
|
||||
// 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.
|
||||
//
|
||||
// This function is part of the walletdb.Bucket interface implementation.
|
||||
func (b *bucket) Delete(key []byte) error {
|
||||
return convertErr((*bolt.Bucket)(b).Delete(key))
|
||||
}
|
||||
|
||||
// transaction represents a database transaction. It can either by read-only or
|
||||
// read-write and implements the walletdb.Bucket interface. The transaction
|
||||
// provides a root bucket against which all read and writes occur.
|
||||
type transaction struct {
|
||||
boltTx *bolt.Tx
|
||||
rootBucket *bolt.Bucket
|
||||
}
|
||||
|
||||
// Enforce transaction implements the walletdb.Tx interface.
|
||||
var _ walletdb.Tx = (*transaction)(nil)
|
||||
|
||||
// RootBucket returns the top-most bucket for the namespace the transaction was
|
||||
// created from.
|
||||
//
|
||||
// This function is part of the walletdb.Tx interface implementation.
|
||||
func (tx *transaction) RootBucket() walletdb.Bucket {
|
||||
return (*bucket)(tx.rootBucket)
|
||||
}
|
||||
|
||||
// Commit commits all changes that have been made through the root bucket and
|
||||
// all of its sub-buckets to persistent storage.
|
||||
//
|
||||
// This function is part of the walletdb.Tx interface implementation.
|
||||
func (tx *transaction) Commit() error {
|
||||
return convertErr(tx.boltTx.Commit())
|
||||
}
|
||||
|
||||
// Rollback undoes all changes that have been made to the root bucket and all of
|
||||
// its sub-buckets.
|
||||
//
|
||||
// This function is part of the walletdb.Tx interface implementation.
|
||||
func (tx *transaction) Rollback() error {
|
||||
return convertErr(tx.boltTx.Rollback())
|
||||
}
|
||||
|
||||
// namespace represents a database namespace that is inteded to support the
|
||||
// concept of a single entity that controls the opening, creating, and closing
|
||||
// of a database while providing other entities their own namespace to work in.
|
||||
// It implements the walletdb.Namespace interface.
|
||||
type namespace struct {
|
||||
db *bolt.DB
|
||||
key []byte
|
||||
}
|
||||
|
||||
// Enforce namespace implements the walletdb.Namespace interface.
|
||||
var _ walletdb.Namespace = (*namespace)(nil)
|
||||
|
||||
// Begin starts a transaction which is either read-only or read-write depending
|
||||
// on the specified flag. Multiple read-only transactions can be started
|
||||
// simultaneously while only a single read-write transaction can be started at a
|
||||
// time. The call will block when starting a read-write transaction when one is
|
||||
// already open.
|
||||
//
|
||||
// NOTE: The transaction must be closed by calling Rollback or Commit on it when
|
||||
// it is no longer needed. Failure to do so will result in unclaimed memory.
|
||||
//
|
||||
// This function is part of the walletdb.Namespace interface implementation.
|
||||
func (ns *namespace) Begin(writable bool) (walletdb.Tx, error) {
|
||||
boltTx, err := ns.db.Begin(writable)
|
||||
if err != nil {
|
||||
return nil, convertErr(err)
|
||||
}
|
||||
|
||||
bucket := boltTx.Bucket(ns.key)
|
||||
if bucket == nil {
|
||||
return nil, walletdb.ErrBucketNotFound
|
||||
}
|
||||
|
||||
return &transaction{boltTx: boltTx, rootBucket: bucket}, nil
|
||||
}
|
||||
|
||||
// View invokes the passed function in the context of a managed read-only
|
||||
// transaction. Any errors returned from the user-supplied function are
|
||||
// returned from this function.
|
||||
//
|
||||
// Calling Rollback on the transaction passed to the user-supplied function will
|
||||
// result in a panic.
|
||||
//
|
||||
// This function is part of the walletdb.Namespace interface implementation.
|
||||
func (ns *namespace) View(fn func(walletdb.Tx) error) error {
|
||||
return convertErr(ns.db.View(func(boltTx *bolt.Tx) error {
|
||||
bucket := boltTx.Bucket(ns.key)
|
||||
if bucket == nil {
|
||||
return walletdb.ErrBucketNotFound
|
||||
}
|
||||
|
||||
return fn(&transaction{boltTx: boltTx, rootBucket: bucket})
|
||||
}))
|
||||
}
|
||||
|
||||
// Update invokes the passed function in the context of a managed read-write
|
||||
// transaction. Any errors returned from the user-supplied function will cause
|
||||
// the transaction to be rolled back and are returned from this function.
|
||||
// Otherwise, the transaction is commited when the user-supplied function
|
||||
// returns a nil error.
|
||||
//
|
||||
// Calling Rollback on the transaction passed to the user-supplied function will
|
||||
// result in a panic.
|
||||
//
|
||||
// This function is part of the walletdb.Namespace interface implementation.
|
||||
func (ns *namespace) Update(fn func(walletdb.Tx) error) error {
|
||||
return convertErr(ns.db.Update(func(boltTx *bolt.Tx) error {
|
||||
bucket := boltTx.Bucket(ns.key)
|
||||
if bucket == nil {
|
||||
return walletdb.ErrBucketNotFound
|
||||
}
|
||||
|
||||
return fn(&transaction{boltTx: boltTx, rootBucket: bucket})
|
||||
}))
|
||||
}
|
||||
|
||||
// db represents a collection of namespaces which are persisted and implements
|
||||
// the walletdb.Db interface. All database access is performed through
|
||||
// transactions which are obtained through the specific Namespace.
|
||||
type db bolt.DB
|
||||
|
||||
// Enforce db implements the walletdb.Db interface.
|
||||
var _ walletdb.DB = (*db)(nil)
|
||||
|
||||
// Namespace returns a Namespace interface for the provided key. See the
|
||||
// Namespace interface documentation for more details. Attempting to access a
|
||||
// Namespace on a database that is not open yet or has been closed will result
|
||||
// in ErrDbNotOpen. Namespaces are created in the database on first access.
|
||||
//
|
||||
// This function is part of the walletdb.Db interface implementation.
|
||||
func (db *db) Namespace(key []byte) (walletdb.Namespace, error) {
|
||||
// Check if the namespace needs to be created using a read-only
|
||||
// transaction. This is done because read-only transactions are faster
|
||||
// and don't block like write transactions.
|
||||
var doCreate bool
|
||||
err := (*bolt.DB)(db).View(func(tx *bolt.Tx) error {
|
||||
boltBucket := tx.Bucket(key)
|
||||
if boltBucket == nil {
|
||||
doCreate = true
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, convertErr(err)
|
||||
}
|
||||
|
||||
// Create the namespace if needed by using an writable update
|
||||
// transaction.
|
||||
if doCreate {
|
||||
err := (*bolt.DB)(db).Update(func(tx *bolt.Tx) error {
|
||||
_, err := tx.CreateBucket(key)
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
return nil, convertErr(err)
|
||||
}
|
||||
}
|
||||
|
||||
return &namespace{db: (*bolt.DB)(db), key: key}, nil
|
||||
}
|
||||
|
||||
// DeleteNamespace deletes the namespace for the passed key. ErrBucketNotFound
|
||||
// will be returned if the namespace does not exist.
|
||||
//
|
||||
// This function is part of the walletdb.Db interface implementation.
|
||||
func (db *db) DeleteNamespace(key []byte) error {
|
||||
return convertErr((*bolt.DB)(db).Update(func(tx *bolt.Tx) error {
|
||||
return tx.DeleteBucket(key)
|
||||
}))
|
||||
}
|
||||
|
||||
// Copy writes a copy of the database to the provided writer. This call will
|
||||
// start a read-only transaction to perform all operations.
|
||||
//
|
||||
// This function is part of the walletdb.Db interface implementation.
|
||||
func (db *db) Copy(w io.Writer) error {
|
||||
return convertErr((*bolt.DB)(db).View(func(tx *bolt.Tx) error {
|
||||
return tx.Copy(w)
|
||||
}))
|
||||
}
|
||||
|
||||
// Close cleanly shuts down the database and syncs all data.
|
||||
//
|
||||
// This function is part of the walletdb.Db interface implementation.
|
||||
func (db *db) Close() error {
|
||||
return convertErr((*bolt.DB)(db).Close())
|
||||
}
|
||||
|
||||
// filesExists reports whether the named file or directory exists.
|
||||
func fileExists(name string) bool {
|
||||
if _, err := os.Stat(name); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// openDB opens the database at the provided path. walletdb.ErrDbDoesNotExist
|
||||
// is returned if the database doesn't exist and the create flag is not set.
|
||||
func openDB(dbPath string, create bool) (walletdb.DB, error) {
|
||||
if !create && !fileExists(dbPath) {
|
||||
return nil, walletdb.ErrDbDoesNotExist
|
||||
}
|
||||
|
||||
boltDB, err := bolt.Open(dbPath, 0600, nil)
|
||||
return (*db)(boltDB), convertErr(err)
|
||||
}
|
37
walletdb/bdb/doc.go
Normal file
37
walletdb/bdb/doc.go
Normal file
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* Copyright (c) 2014 Conformal Systems LLC <info@conformal.com>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
/*
|
||||
Package bdb implements an instance of walletdb that uses boltdb for the backing
|
||||
datastore.
|
||||
|
||||
Usage
|
||||
|
||||
This package is only a driver to the walletdb package and provides the database
|
||||
type of "bdb". The only parameter the Open and Create functions take is the
|
||||
database path as a string:
|
||||
|
||||
db, err := walletdb.Open("bdb", "path/to/database.db")
|
||||
if err != nil {
|
||||
// Handle error
|
||||
}
|
||||
|
||||
db, err := walletdb.Create("bdb", "path/to/database.db")
|
||||
if err != nil {
|
||||
// Handle error
|
||||
}
|
||||
*/
|
||||
package bdb
|
78
walletdb/bdb/driver.go
Normal file
78
walletdb/bdb/driver.go
Normal file
|
@ -0,0 +1,78 @@
|
|||
/*
|
||||
* Copyright (c) 2014 Conformal Systems LLC <info@conformal.com>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
package bdb
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/conformal/btcwallet/walletdb"
|
||||
)
|
||||
|
||||
const (
|
||||
dbType = "bdb"
|
||||
)
|
||||
|
||||
// parseArgs parses the arguments from the walletdb Open/Create methods.
|
||||
func parseArgs(funcName string, args ...interface{}) (string, error) {
|
||||
if len(args) != 1 {
|
||||
return "", fmt.Errorf("invalid arguments to %s.%s -- "+
|
||||
"expected database path", dbType, funcName)
|
||||
}
|
||||
|
||||
dbPath, ok := args[0].(string)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("first argument to %s.%s is invalid -- "+
|
||||
"expected database path string", dbType, funcName)
|
||||
}
|
||||
|
||||
return dbPath, nil
|
||||
}
|
||||
|
||||
// openDBDriver is the callback provided during driver registration that opens
|
||||
// an existing database for use.
|
||||
func openDBDriver(args ...interface{}) (walletdb.DB, error) {
|
||||
dbPath, err := parseArgs("Open", args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return openDB(dbPath, false)
|
||||
}
|
||||
|
||||
// createDBDriver is the callback provided during driver registration that
|
||||
// creates, initializes, and opens a database for use.
|
||||
func createDBDriver(args ...interface{}) (walletdb.DB, error) {
|
||||
dbPath, err := parseArgs("Create", args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return openDB(dbPath, true)
|
||||
}
|
||||
|
||||
func init() {
|
||||
// Register the driver.
|
||||
driver := walletdb.Driver{
|
||||
DbType: dbType,
|
||||
Create: createDBDriver,
|
||||
Open: openDBDriver,
|
||||
}
|
||||
if err := walletdb.RegisterDriver(driver); err != nil {
|
||||
panic(fmt.Sprintf("Failed to regiser database driver '%s': %v",
|
||||
dbType, err))
|
||||
}
|
||||
}
|
115
walletdb/doc.go
Normal file
115
walletdb/doc.go
Normal file
|
@ -0,0 +1,115 @@
|
|||
/*
|
||||
* Copyright (c) 2014 Conformal Systems LLC <info@conformal.com>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
/*
|
||||
Package walletdb provides a namespaced database interface for btcwallet.
|
||||
|
||||
Overview
|
||||
|
||||
A wallet essentially consists of a multitude of stored data such as private
|
||||
and public keys, key derivation bits, pay-to-script-hash scripts, and various
|
||||
metadata. One of the issues with many wallets is they are tightly integrated.
|
||||
Designing a wallet with loosely coupled components that provide specific
|
||||
functionality is ideal, however it presents a challenge in regards to data
|
||||
storage since each component needs to store its own data without knowing the
|
||||
internals of other components or breaking atomicity.
|
||||
|
||||
This package solves this issue by providing a pluggable driver, namespaced
|
||||
database interface that is intended to be used by the main wallet daemon. This
|
||||
allows the potential for any backend database type with a suitable driver. Each
|
||||
component, which will typically be a package, can then implement various
|
||||
functionality such as address management, voting pools, and colored coin
|
||||
metadata in their own namespace without having to worry about conflicts with
|
||||
other packages even though they are sharing the same database that is managed by
|
||||
the wallet.
|
||||
|
||||
A quick overview of the features walletdb provides are as follows:
|
||||
|
||||
- Key/value store
|
||||
- Namespace support
|
||||
- Allows multiple packages to have their own area in the database without
|
||||
worrying about conflicts
|
||||
- Read-only and read-write transactions with both manual and managed modes
|
||||
- Nested buckets
|
||||
- Supports registration of backend databases
|
||||
- Comprehensive test coverage
|
||||
|
||||
Database
|
||||
|
||||
The main entry point is the DB interface. It exposes functionality for
|
||||
creating, retrieving, and removing namespaces. It is obtained via the Create
|
||||
and Open functions which take a database type string that identifies the
|
||||
specific database driver (backend) to use as well as arguments specific to the
|
||||
specified driver.
|
||||
|
||||
Namespaces
|
||||
|
||||
The Namespace interface is an abstraction that provides facilities for obtaining
|
||||
transactions (the Tx interface) that are the basis of all database reads and
|
||||
writes. Unlike some database interfaces that support reading and writing
|
||||
without transactions, this interface requires transactions even when only
|
||||
reading or writing a single key.
|
||||
|
||||
The Begin function provides an unmanaged transaction while the View and Update
|
||||
functions provide a managed transaction. These are described in more detail
|
||||
below.
|
||||
|
||||
Transactions
|
||||
|
||||
The Tx interface provides facilities for rolling back or commiting changes that
|
||||
took place while the transaction was active. It also provides the root bucket
|
||||
under which all keys, values, and nested buckets are stored. A transaction
|
||||
can either be read-only or read-write and managed or unmanaged.
|
||||
|
||||
Managed versus Unmanaged Transactions
|
||||
|
||||
A managed transaction is one where the caller provides a function to execute
|
||||
within the context of the transaction and the commit or rollback is handled
|
||||
automatically depending on whether or not the provided function returns an
|
||||
error. Attempting to manually call Rollback or Commit on the managed
|
||||
transaction will result in a panic.
|
||||
|
||||
An unmanaged transaction, on the other hand, requires the caller to manually
|
||||
call Commit or Rollback when they are finished with it. Leaving transactions
|
||||
open for long periods of time can have several adverse effects, so it is
|
||||
recommended that managed transactions are used instead.
|
||||
|
||||
Buckets
|
||||
|
||||
The Bucket interface provides the ability to manipulate key/value pairs and
|
||||
nested buckets as well as iterate through them.
|
||||
|
||||
The Get, Put, and Delete functions work with key/value pairs, while the Bucket,
|
||||
CreateBucket, CreateBucketIfNotExists, and DeleteBucket functions work with
|
||||
buckets. The ForEach function allows the caller to provide a function to be
|
||||
called with each key/value pair and nested bucket in the current bucket.
|
||||
|
||||
Root Bucket
|
||||
|
||||
As discussed above, all of the functions which are used to manipulate key/value
|
||||
pairs and nested buckets exist on the Bucket interface. The root bucket is the
|
||||
upper-most bucket in a namespace under which data is stored and is created at
|
||||
the same time as the namespace. Use the RootBucket function on the Tx interface
|
||||
to retrieve it.
|
||||
|
||||
Nested Buckets
|
||||
|
||||
The CreateBucket and CreateBucketIfNotExists functions on the Bucket interface
|
||||
provide the ability to create an arbitrary number of nested buckets. It is
|
||||
a good idea to avoid a lot of buckets with little data in them as it could lead
|
||||
to poor page utilization depending on the specific driver in use.
|
||||
*/
|
||||
package walletdb
|
92
walletdb/error.go
Normal file
92
walletdb/error.go
Normal file
|
@ -0,0 +1,92 @@
|
|||
/*
|
||||
* Copyright (c) 2014 Conformal Systems LLC <info@conformal.com>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
package walletdb
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
// Errors that can occur during driver registration.
|
||||
var (
|
||||
// ErrDbTypeRegistered is returned when two different database drivers
|
||||
// attempt to register with the name database type.
|
||||
ErrDbTypeRegistered = errors.New("database type already registered")
|
||||
)
|
||||
|
||||
// Errors that the various database functions may return.
|
||||
var (
|
||||
// ErrDbUnknownType is returned when there is no driver registered for
|
||||
// the specified database type.
|
||||
ErrDbUnknownType = errors.New("unknown database type")
|
||||
|
||||
// ErrDbDoesNotExist is returned when open is called for a database that
|
||||
// does not exist.
|
||||
ErrDbDoesNotExist = errors.New("database does not exist")
|
||||
|
||||
// ErrDbExists is returned when create is called for a database that
|
||||
// already exists.
|
||||
ErrDbExists = errors.New("database already exists")
|
||||
|
||||
// ErrDbNotOpen is returned when a database instance is accessed before
|
||||
// it is opened or after it is closed.
|
||||
ErrDbNotOpen = errors.New("database not open")
|
||||
|
||||
// ErrDbAlreadyOpen is returned when open is called on a database that
|
||||
// is already open.
|
||||
ErrDbAlreadyOpen = errors.New("database already open")
|
||||
|
||||
// ErrInvalid is returned if the specified database is not valid.
|
||||
ErrInvalid = errors.New("invalid database")
|
||||
)
|
||||
|
||||
// Errors that can occur when beginning or committing a transaction.
|
||||
var (
|
||||
// ErrTxClosed is returned when attempting to commit or rollback a
|
||||
// transaction that has already had one of those operations performed.
|
||||
ErrTxClosed = errors.New("tx closed")
|
||||
|
||||
// ErrTxNotWritable is returned when an operation that requires write
|
||||
// access to the database is attempted against a read-only transaction.
|
||||
ErrTxNotWritable = errors.New("tx not writable")
|
||||
)
|
||||
|
||||
// Errors that can occur when putting or deleting a value or bucket.
|
||||
var (
|
||||
// ErrBucketNotFound is returned when trying to access a bucket that has
|
||||
// not been created yet.
|
||||
ErrBucketNotFound = errors.New("bucket not found")
|
||||
|
||||
// ErrBucketExists is returned when creating a bucket that already exists.
|
||||
ErrBucketExists = errors.New("bucket already exists")
|
||||
|
||||
// ErrBucketNameRequired is returned when creating a bucket with a blank name.
|
||||
ErrBucketNameRequired = errors.New("bucket name required")
|
||||
|
||||
// ErrKeyRequired is returned when inserting a zero-length key.
|
||||
ErrKeyRequired = errors.New("key required")
|
||||
|
||||
// ErrKeyTooLarge is returned when inserting a key that is larger than MaxKeySize.
|
||||
ErrKeyTooLarge = errors.New("key too large")
|
||||
|
||||
// ErrValueTooLarge is returned when inserting a value that is larger than MaxValueSize.
|
||||
ErrValueTooLarge = errors.New("value too large")
|
||||
|
||||
// ErrIncompatibleValue is returned when trying create or delete a
|
||||
// bucket on an existing non-bucket key or when trying to create or
|
||||
// delete a non-bucket key on an existing bucket key.
|
||||
ErrIncompatibleValue = errors.New("incompatible value")
|
||||
)
|
235
walletdb/interface.go
Normal file
235
walletdb/interface.go
Normal file
|
@ -0,0 +1,235 @@
|
|||
/*
|
||||
* Copyright (c) 2014 Conformal Systems LLC <info@conformal.com>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
// This interface was inspired heavily by the excellent boltdb project at
|
||||
// https://github.com/boltdb/bolt by Ben B. Johnson.
|
||||
|
||||
package walletdb
|
||||
|
||||
import "io"
|
||||
|
||||
// Bucket represents a collection of key/value pairs.
|
||||
type Bucket interface {
|
||||
// Bucket retrieves a nested bucket with the given key. Returns nil if
|
||||
// the bucket does not exist.
|
||||
Bucket(key []byte) Bucket
|
||||
|
||||
// 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) (Bucket, 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) (Bucket, error)
|
||||
|
||||
// DeleteBucket 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.
|
||||
DeleteBucket(key []byte) error
|
||||
|
||||
// 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
|
||||
|
||||
// Writable returns whether or not the bucket is writable.
|
||||
Writable() bool
|
||||
|
||||
// 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
|
||||
|
||||
// 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
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// Tx represents a database transaction. It can either by read-only or
|
||||
// read-write. The transaction provides a root bucket against which all read
|
||||
// and writes occur.
|
||||
//
|
||||
// As would be expected with a transaction, no changes will be saved to the
|
||||
// database until it has been committed. The transaction will only provide a
|
||||
// view of the database at the time it was created. Transactions should not be
|
||||
// long running operations.
|
||||
type Tx interface {
|
||||
// RootBucket returns the top-most bucket for the namespace the
|
||||
// transaction was created from.
|
||||
RootBucket() Bucket
|
||||
|
||||
// Commit commits all changes that have been made through the root
|
||||
// bucket and all of its sub-buckets to persistent storage.
|
||||
Commit() error
|
||||
|
||||
// Rollback undoes all changes that have been made to the root bucket
|
||||
// and all of its sub-buckets.
|
||||
Rollback() error
|
||||
}
|
||||
|
||||
// Namespace represents a database namespace that is inteded to support the
|
||||
// concept of a single entity that controls the opening, creating, and closing
|
||||
// of a database while providing other entities their own namespace to work in.
|
||||
type Namespace interface {
|
||||
// Begin starts a transaction which is either read-only or read-write
|
||||
// depending on the specified flag. Multiple read-only transactions
|
||||
// can be started simultaneously while only a single read-write
|
||||
// transaction can be started at a time. The call will block when
|
||||
// starting a read-write transaction when one is already open.
|
||||
//
|
||||
// NOTE: The transaction must be closed by calling Rollback or Commit on
|
||||
// it when it is no longer needed. Failure to do so can result in
|
||||
// unclaimed memory depending on the specific database implementation.
|
||||
Begin(writable bool) (Tx, error)
|
||||
|
||||
// View invokes the passed function in the context of a managed
|
||||
// read-only transaction. Any errors returned from the user-supplied
|
||||
// function are returned from this function.
|
||||
//
|
||||
// Calling Rollback on the transaction passed to the user-supplied
|
||||
// function will result in a panic.
|
||||
View(fn func(Tx) error) error
|
||||
|
||||
// Update invokes the passed function in the context of a managed
|
||||
// read-write transaction. Any errors returned from the user-supplied
|
||||
// function will cause the transaction to be rolled back and are
|
||||
// returned from this function. Otherwise, the transaction is commited
|
||||
// when the user-supplied function returns a nil error.
|
||||
//
|
||||
// Calling Rollback on the transaction passed to the user-supplied
|
||||
// function will result in a panic.
|
||||
Update(fn func(Tx) error) error
|
||||
}
|
||||
|
||||
// DB represents a collection of namespaces which are persisted. All database
|
||||
// access is performed through transactions which are obtained through the
|
||||
// specific Namespace.
|
||||
type DB interface {
|
||||
// Namespace returns a Namespace interface for the provided key. See
|
||||
// the Namespace interface documentation for more details. Attempting
|
||||
// to access a Namespace on a database that is not open yet or has been
|
||||
// closed will result in ErrDbNotOpen. Namespaces are created in the
|
||||
// database on first access.
|
||||
Namespace(key []byte) (Namespace, error)
|
||||
|
||||
// DeleteNamespace deletes the namespace for the passed key.
|
||||
// ErrBucketNotFound will be returned if the namespace does not exist.
|
||||
DeleteNamespace(key []byte) 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
|
||||
}
|
||||
|
||||
// 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...)
|
||||
}
|
Loading…
Add table
Reference in a new issue