/
interface.go
320 lines (262 loc) · 11.4 KB
/
interface.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
// Copyright (c) 2014 The btcsuite developers
// Copyright (c) 2015 The Decred 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 (
"context"
"io"
"runtime/trace"
"github.com/EXCCoin/exccwallet/v2/errors"
)
// 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
// 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 is 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
}
// 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.
// Errors with code Exist if the bucket already exists and Invalid if the
// key is empty or otherwise invalid for the driver.
CreateBucket(key []byte) (ReadWriteBucket, error)
// CreateBucketIfNotExists creates and returns a new nested bucket with the
// given key if it does not already exist. Errors with code Invalid if the
// key is empty or the key/value is not valid for the driver.
CreateBucketIfNotExists(key []byte) (ReadWriteBucket, error)
// DeleteNestedBucket removes a nested bucket with the given key. Errors
// with code Invalid if attempted against a read-only transaction and
// NotExist 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.
// Errors with code Invalid 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. Errors with code Invalid if
// attempted against a read-only transaction.
Delete(key []byte) error
// Cursor returns a new cursor, allowing for iteration over the bucket's
// key/value pairs and nested buckets in forward or backward order.
// Only one cursor can be opened at a time and should be closed before
// committing or rolling back the transaction.
ReadWriteCursor() ReadWriteCursor
}
// 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)
// Close closes the cursor. Cursors must be closed before opening a new
// cursor and before finishing a transaction.
Close()
}
// 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. Errors with code Invalid 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 {
cursor := bucket.ReadCursor()
k, v := cursor.First()
cursor.Close()
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
}
// View opens a database read transaction and executes the function f with the
// transaction passed as a parameter. After f exits or panics, the transaction
// is rolled back. If f errors, its error is returned, not a rollback error (if
// any occurred).
func View(ctx context.Context, db DB, f func(tx ReadTx) error) error {
defer trace.StartRegion(ctx, "db.View").End()
tx, err := db.BeginReadTx()
if err != nil {
return err
}
defer trace.StartRegion(ctx, "db.ReadTx").End()
// Rollback the transaction after f returns or panics. Do not recover from
// any panic to keep the original stack trace intact.
defer func() {
rollbackErr := tx.Rollback()
if err != nil {
err = rollbackErr
}
}()
return f(tx)
}
// 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 or panic, the
// transaction is rolled back. If a rollback fails, the original error returned
// by f is still returned. If the commit fails, the commit error is returned.
func Update(ctx context.Context, db DB, f func(tx ReadWriteTx) error) (err error) {
defer trace.StartRegion(ctx, "db.Update").End()
tx, err := db.BeginReadWriteTx()
if err != nil {
return err
}
defer trace.StartRegion(ctx, "db.ReadWriteTx").End()
// Commit or rollback the transaction after f returns or panics. Do not
// recover from the panic to keep the original stack trace intact.
panicked := true
defer func() {
if panicked || err != nil {
tx.Rollback()
return
}
err = tx.Commit()
}()
err = f(tx)
panicked = false
return err
}
// 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.
Create func(args ...interface{}) (DB, error)
// Open is the function that will be invoked with all user-specified
// arguments to open the database.
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.
// Errors if the will be returned if the database type for the driver has
// already been registered.
func RegisterDriver(driver Driver) error {
const op errors.Op = "walletdb.RegisterDriver"
if _, exists := drivers[driver.DbType]; exists {
return errors.E(op, errors.Exist, errors.Errorf("driver %q is already registered", driver.DbType))
}
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.
func Create(dbType string, args ...interface{}) (DB, error) {
const op errors.Op = "walletdb.Create"
drv, exists := drivers[dbType]
if !exists {
return nil, errors.E(op, errors.Invalid, errors.Errorf("driver %q is not registered", dbType))
}
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.
func Open(dbType string, args ...interface{}) (DB, error) {
const op errors.Op = "walletdb.Open"
drv, exists := drivers[dbType]
if !exists {
return nil, errors.E(op, errors.Invalid, errors.Errorf("driver %q is not registered", dbType))
}
return drv.Open(args...)
}