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