Revert "Refactor Transaction/Bucket API."
This reverts commit 1ad2b99f281d587b767b36f886401e81d17915a9.
Ben Johnson
10 years ago
0 | TODO | |
1 | ==== | |
2 | X Open DB. | |
3 | X Initialize transaction. | |
4 | - Cursor First, Get(key), Next | |
5 | - RWTransaction.insert() | |
6 | - rebalance | |
7 | - adjust cursors | |
8 | - RWTransaction Commmit | |
9 | ||
10 | ||
11 | ||
12 |
0 | 0 | package bolt |
1 | 1 | |
2 | import ( | |
3 | "bytes" | |
4 | ) | |
5 | ||
6 | 2 | // Bucket represents a collection of key/value pairs inside the database. |
7 | // All keys inside the bucket are unique. | |
8 | // | |
9 | // Accessing or changing data from a Bucket whose Transaction has closed will cause a panic. | |
3 | // All keys inside the bucket are unique. The Bucket type is not typically used | |
4 | // directly. Instead the bucket name is typically passed into the Get(), Put(), | |
5 | // or Delete() functions. | |
10 | 6 | type Bucket struct { |
11 | 7 | *bucket |
12 | 8 | name string |
24 | 20 | return b.name |
25 | 21 | } |
26 | 22 | |
27 | // Cursor creates a new cursor for this bucket. | |
28 | func (b *Bucket) Cursor() *Cursor { | |
29 | _assert(b.transaction.isOpen(), "transaction not open") | |
23 | // cursor creates a new cursor for this bucket. | |
24 | func (b *Bucket) cursor() *Cursor { | |
30 | 25 | return &Cursor{ |
31 | 26 | transaction: b.transaction, |
32 | 27 | root: b.root, |
34 | 29 | } |
35 | 30 | } |
36 | 31 | |
37 | // Get retrieves the value for a key in a named bucket. | |
38 | // Returns a nil value if the key does not exist. | |
39 | func (b *Bucket) Get(key []byte) []byte { | |
40 | _assert(b.transaction.isOpen(), "transaction not open") | |
41 | c := b.Cursor() | |
42 | k, v := c.Seek(key) | |
43 | ||
44 | // If our target node isn't the same key as what's passed in then return nil. | |
45 | if !bytes.Equal(key, k) { | |
46 | return nil | |
47 | } | |
48 | ||
49 | return v | |
50 | } | |
51 | ||
52 | // Put sets the value for a key inside of the bucket. | |
53 | // If the key exist then its previous value will be overwritten. | |
54 | // Returns an error if bucket was created from a read-only transaction, if the | |
55 | // key is blank, if the key is too large, or if the value is too large. | |
56 | func (b *Bucket) Put(key []byte, value []byte) error { | |
57 | _assert(b.transaction.isOpen(), "transaction not open") | |
58 | if !b.transaction.writable { | |
59 | return ErrTransactionNotWritable | |
60 | } else if len(key) == 0 { | |
61 | return ErrKeyRequired | |
62 | } else if len(key) > MaxKeySize { | |
63 | return ErrKeyTooLarge | |
64 | } else if len(value) > MaxValueSize { | |
65 | return ErrValueTooLarge | |
66 | } | |
67 | ||
68 | // Move cursor to correct position. | |
69 | c := b.Cursor() | |
70 | c.Seek(key) | |
71 | ||
72 | // Insert the key/value. | |
73 | c.node(b.transaction).put(key, key, value, 0) | |
74 | ||
75 | return nil | |
76 | } | |
77 | ||
78 | // Delete removes a key from the bucket. | |
79 | // If the key does not exist then nothing is done and a nil error is returned. | |
80 | // Returns an error if the bucket was created from a read-only transaction. | |
81 | func (b *Bucket) Delete(key []byte) error { | |
82 | _assert(b.transaction.isOpen(), "transaction not open") | |
83 | if !b.transaction.writable { | |
84 | return ErrTransactionNotWritable | |
85 | } | |
86 | ||
87 | // Move cursor to correct position. | |
88 | c := b.Cursor() | |
89 | c.Seek(key) | |
90 | ||
91 | // Delete the node if we have a matching key. | |
92 | c.node(c.transaction).del(key) | |
93 | ||
94 | return nil | |
95 | } | |
96 | ||
97 | // NextSequence returns an autoincrementing integer for the bucket. | |
98 | // Returns an error if the bucket was created from a read-only transaction or | |
99 | // if the next sequence will overflow the int type. | |
100 | func (b *Bucket) NextSequence() (int, error) { | |
101 | _assert(b.transaction.isOpen(), "transaction not open") | |
102 | if !b.transaction.writable { | |
103 | return 0, ErrTransactionNotWritable | |
104 | } else if b.bucket.sequence == uint64(maxInt) { | |
105 | return 0, ErrSequenceOverflow | |
106 | } | |
107 | ||
108 | // Increment and return the sequence. | |
109 | b.bucket.sequence++ | |
110 | ||
111 | return int(b.bucket.sequence), nil | |
112 | } | |
113 | ||
114 | // ForEach executes a function for each key/value pair in a bucket. | |
115 | func (b *Bucket) ForEach(fn func(k, v []byte) error) error { | |
116 | _assert(b.transaction.isOpen(), "transaction not open") | |
117 | c := b.Cursor() | |
118 | for k, v := c.First(); k != nil; k, v = c.Next() { | |
119 | if err := fn(k, v); err != nil { | |
120 | return err | |
121 | } | |
122 | } | |
123 | return nil | |
124 | } | |
125 | ||
126 | 32 | // Stat returns stats on a bucket. |
127 | 33 | func (b *Bucket) Stat() *BucketStat { |
128 | _assert(b.transaction.isOpen(), "transaction not open") | |
129 | 34 | s := &BucketStat{} |
130 | 35 | b.transaction.forEachPage(b.root, 0, func(p *page, depth int) { |
131 | 36 | if (p.flags & leafPageFlag) != 0 { |
10 | 10 | // Ensure a bucket can calculate stats. |
11 | 11 | func TestBucketStat(t *testing.T) { |
12 | 12 | withOpenDB(func(db *DB, path string) { |
13 | db.Do(func(txn *Transaction) error { | |
13 | db.Do(func(txn *RWTransaction) error { | |
14 | 14 | // Add bucket with lots of keys. |
15 | 15 | txn.CreateBucket("widgets") |
16 | b := txn.Bucket("widgets") | |
17 | 16 | for i := 0; i < 100000; i++ { |
18 | b.Put([]byte(strconv.Itoa(i)), []byte(strconv.Itoa(i))) | |
17 | txn.Put("widgets", []byte(strconv.Itoa(i)), []byte(strconv.Itoa(i))) | |
19 | 18 | } |
20 | 19 | |
21 | 20 | // Add bucket with fewer keys but one big value. |
22 | 21 | txn.CreateBucket("woojits") |
23 | b = txn.Bucket("woojits") | |
24 | 22 | for i := 0; i < 500; i++ { |
25 | b.Put([]byte(strconv.Itoa(i)), []byte(strconv.Itoa(i))) | |
23 | txn.Put("woojits", []byte(strconv.Itoa(i)), []byte(strconv.Itoa(i))) | |
26 | 24 | } |
27 | b.Put([]byte("really-big-value"), []byte(strings.Repeat("*", 10000))) | |
25 | txn.Put("woojits", []byte("really-big-value"), []byte(strings.Repeat("*", 10000))) | |
28 | 26 | |
29 | 27 | // Add a bucket that fits on a single root leaf. |
30 | 28 | txn.CreateBucket("whozawhats") |
31 | b = txn.Bucket("whozawhats") | |
32 | b.Put([]byte("foo"), []byte("bar")) | |
29 | txn.Put("whozawhats", []byte("foo"), []byte("bar")) | |
33 | 30 | |
34 | 31 | return nil |
35 | 32 | }) |
200 | 200 | } |
201 | 201 | |
202 | 202 | // node returns the node that the cursor is currently positioned on. |
203 | func (c *Cursor) node(t *Transaction) *node { | |
203 | func (c *Cursor) node(t *RWTransaction) *node { | |
204 | 204 | _assert(len(c.stack) > 0, "accessing a node with a zero-length cursor stack") |
205 | 205 | |
206 | 206 | // Start from root and traverse down the hierarchy. |
15 | 15 | const maxMmapStep = 1 << 30 // 1GB |
16 | 16 | |
17 | 17 | // DB represents a collection of buckets persisted to a file on disk. |
18 | // All data access is performed through transactions which can be obtained from | |
19 | // the DB. There are a number of functions duplicated from the Transction type | |
20 | // which provide ease-of-use, single transaction access to the data. | |
21 | // | |
22 | // All the functions on DB will return a ErrDatabaseNotOpen if accessed before | |
23 | // Open() is called or after Close is called. | |
18 | // All data access is performed through transactions which can be obtained through the DB. | |
19 | // All the functions on DB will return a ErrDatabaseNotOpen if accessed before Open() is called. | |
24 | 20 | type DB struct { |
25 | 21 | os _os |
26 | 22 | syscall _syscall |
32 | 28 | meta1 *meta |
33 | 29 | pageSize int |
34 | 30 | opened bool |
35 | rwtransaction *Transaction | |
31 | rwtransaction *RWTransaction | |
36 | 32 | transactions []*Transaction |
37 | 33 | freelist *freelist |
38 | 34 | |
44 | 40 | // Path returns the path to currently open database file. |
45 | 41 | func (db *DB) Path() string { |
46 | 42 | return db.path |
43 | } | |
44 | ||
45 | // GoString returns the Go string representation of the database. | |
46 | func (db *DB) GoString() string { | |
47 | return fmt.Sprintf("bolt.DB{path:%q}", db.path) | |
48 | } | |
49 | ||
50 | // String returns the string representation of the database. | |
51 | func (db *DB) String() string { | |
52 | return fmt.Sprintf("DB<%q>", db.path) | |
47 | 53 | } |
48 | 54 | |
49 | 55 | // Open opens a data file at the given path and initializes the database. |
255 | 261 | // Transaction creates a read-only transaction. |
256 | 262 | // Multiple read-only transactions can be used concurrently. |
257 | 263 | // |
258 | // IMPORTANT: You must close the transaction after you are finished or else the | |
259 | // database will not reclaim old pages. | |
264 | // IMPORTANT: You must close the transaction after you are finished or else the database will not reclaim old pages. | |
260 | 265 | func (db *DB) Transaction() (*Transaction, error) { |
261 | 266 | db.metalock.Lock() |
262 | 267 | defer db.metalock.Unlock() |
283 | 288 | |
284 | 289 | // RWTransaction creates a read/write transaction. |
285 | 290 | // Only one read/write transaction is allowed at a time. |
286 | // You must call Commit() or Close() on the transaction to close it. | |
287 | func (db *DB) RWTransaction() (*Transaction, error) { | |
291 | // You must call Commit() or Rollback() on the transaction to close it. | |
292 | func (db *DB) RWTransaction() (*RWTransaction, error) { | |
288 | 293 | db.metalock.Lock() |
289 | 294 | defer db.metalock.Unlock() |
290 | 295 | |
291 | // Obtain writer lock. This is released by the writer transaction when it closes. | |
296 | // Obtain writer lock. This is released by the RWTransaction when it closes. | |
292 | 297 | db.rwlock.Lock() |
293 | 298 | |
294 | 299 | // Exit if the database is not open yet. |
297 | 302 | return nil, ErrDatabaseNotOpen |
298 | 303 | } |
299 | 304 | |
300 | // Create a writable transaction associated with the database. | |
301 | t := &Transaction{writable: true, nodes: make(map[pgid]*node)} | |
305 | // Create a transaction associated with the database. | |
306 | t := &RWTransaction{nodes: make(map[pgid]*node)} | |
302 | 307 | t.init(db) |
303 | 308 | db.rwtransaction = t |
304 | 309 | |
333 | 338 | } |
334 | 339 | } |
335 | 340 | |
336 | // Do executes a function within the context of a writable Transaction. | |
341 | // Do executes a function within the context of a RWTransaction. | |
337 | 342 | // If no error is returned from the function then the transaction is committed. |
338 | 343 | // If an error is returned then the entire transaction is rolled back. |
339 | 344 | // Any error that is returned from the function or returned from the commit is |
340 | 345 | // returned from the Do() method. |
341 | func (db *DB) Do(fn func(*Transaction) error) error { | |
346 | func (db *DB) Do(fn func(*RWTransaction) error) error { | |
342 | 347 | t, err := db.RWTransaction() |
343 | 348 | if err != nil { |
344 | 349 | return err |
360 | 365 | if err != nil { |
361 | 366 | return err |
362 | 367 | } |
363 | defer t.Rollback() | |
368 | defer t.Close() | |
364 | 369 | |
365 | 370 | // If an error is returned from the function then pass it through. |
366 | 371 | return fn(t) |
370 | 375 | // An error is returned if the bucket cannot be found. |
371 | 376 | func (db *DB) ForEach(name string, fn func(k, v []byte) error) error { |
372 | 377 | return db.With(func(t *Transaction) error { |
373 | b := t.Bucket(name) | |
374 | if b == nil { | |
375 | return ErrBucketNotFound | |
376 | } | |
377 | return b.ForEach(fn) | |
378 | return t.ForEach(name, fn) | |
378 | 379 | }) |
379 | 380 | } |
380 | 381 | |
381 | 382 | // Bucket retrieves a reference to a bucket. |
382 | 383 | // This is typically useful for checking the existence of a bucket. |
383 | // | |
384 | // Do not use the returned bucket for accessing or changing data. | |
385 | 384 | func (db *DB) Bucket(name string) (*Bucket, error) { |
386 | 385 | t, err := db.Transaction() |
387 | 386 | if err != nil { |
388 | 387 | return nil, err |
389 | 388 | } |
390 | defer t.Rollback() | |
389 | defer t.Close() | |
391 | 390 | return t.Bucket(name), nil |
392 | 391 | } |
393 | 392 | |
394 | 393 | // Buckets retrieves a list of all buckets in the database. |
395 | // | |
396 | // Do not use any of the returned buckets for accessing or changing data. | |
397 | 394 | func (db *DB) Buckets() ([]*Bucket, error) { |
398 | 395 | t, err := db.Transaction() |
399 | 396 | if err != nil { |
400 | 397 | return nil, err |
401 | 398 | } |
402 | defer t.Rollback() | |
399 | defer t.Close() | |
403 | 400 | return t.Buckets(), nil |
404 | 401 | } |
405 | 402 | |
407 | 404 | // This function can return an error if the bucket already exists, if the name |
408 | 405 | // is blank, or the bucket name is too long. |
409 | 406 | func (db *DB) CreateBucket(name string) error { |
410 | return db.Do(func(t *Transaction) error { | |
407 | return db.Do(func(t *RWTransaction) error { | |
411 | 408 | return t.CreateBucket(name) |
412 | 409 | }) |
413 | 410 | } |
415 | 412 | // CreateBucketIfNotExists creates a new bucket with the given name if it doesn't already exist. |
416 | 413 | // This function can return an error if the name is blank, or the bucket name is too long. |
417 | 414 | func (db *DB) CreateBucketIfNotExists(name string) error { |
418 | return db.Do(func(t *Transaction) error { | |
415 | return db.Do(func(t *RWTransaction) error { | |
419 | 416 | return t.CreateBucketIfNotExists(name) |
420 | 417 | }) |
421 | 418 | } |
423 | 420 | // DeleteBucket removes a bucket from the database. |
424 | 421 | // Returns an error if the bucket does not exist. |
425 | 422 | func (db *DB) DeleteBucket(name string) error { |
426 | return db.Do(func(t *Transaction) error { | |
423 | return db.Do(func(t *RWTransaction) error { | |
427 | 424 | return t.DeleteBucket(name) |
428 | 425 | }) |
429 | 426 | } |
432 | 429 | // This function can return an error if the bucket does not exist. |
433 | 430 | func (db *DB) NextSequence(name string) (int, error) { |
434 | 431 | var seq int |
435 | err := db.Do(func(t *Transaction) error { | |
436 | b := t.Bucket(name) | |
437 | if b == nil { | |
438 | return ErrBucketNotFound | |
439 | } | |
440 | ||
432 | err := db.Do(func(t *RWTransaction) error { | |
441 | 433 | var err error |
442 | if seq, err = b.NextSequence(); err != nil { | |
443 | return err | |
444 | } | |
445 | return nil | |
434 | seq, err = t.NextSequence(name) | |
435 | return err | |
446 | 436 | }) |
447 | 437 | if err != nil { |
448 | 438 | return 0, err |
451 | 441 | } |
452 | 442 | |
453 | 443 | // Get retrieves the value for a key in a bucket. |
454 | // Returns an error if the bucket does not exist. | |
444 | // Returns an error if the key does not exist. | |
455 | 445 | func (db *DB) Get(name string, key []byte) ([]byte, error) { |
456 | 446 | t, err := db.Transaction() |
457 | 447 | if err != nil { |
458 | 448 | return nil, err |
459 | 449 | } |
460 | defer t.Rollback() | |
461 | ||
462 | b := t.Bucket(name) | |
463 | if b == nil { | |
464 | return nil, ErrBucketNotFound | |
465 | } | |
466 | ||
467 | return b.Get(key), nil | |
450 | defer t.Close() | |
451 | return t.Get(name, key) | |
468 | 452 | } |
469 | 453 | |
470 | 454 | // Put sets the value for a key in a bucket. |
471 | 455 | // Returns an error if the bucket is not found, if key is blank, if the key is too large, or if the value is too large. |
472 | 456 | func (db *DB) Put(name string, key []byte, value []byte) error { |
473 | return db.Do(func(t *Transaction) error { | |
474 | b := t.Bucket(name) | |
475 | if b == nil { | |
476 | return ErrBucketNotFound | |
477 | } | |
478 | return b.Put(key, value) | |
457 | return db.Do(func(t *RWTransaction) error { | |
458 | return t.Put(name, key, value) | |
479 | 459 | }) |
480 | 460 | } |
481 | 461 | |
482 | 462 | // Delete removes a key from a bucket. |
483 | 463 | // Returns an error if the bucket cannot be found. |
484 | 464 | func (db *DB) Delete(name string, key []byte) error { |
485 | return db.Do(func(t *Transaction) error { | |
486 | b := t.Bucket(name) | |
487 | if b == nil { | |
488 | return ErrBucketNotFound | |
489 | } | |
490 | return b.Delete(key) | |
465 | return db.Do(func(t *RWTransaction) error { | |
466 | return t.Delete(name, key) | |
491 | 467 | }) |
492 | 468 | } |
493 | 469 | |
500 | 476 | if err != nil { |
501 | 477 | return err |
502 | 478 | } |
503 | defer t.Commit() | |
479 | defer t.Close() | |
504 | 480 | |
505 | 481 | // Open reader on the database. |
506 | 482 | f, err := os.Open(db.path) |
545 | 521 | db.mmaplock.RUnlock() |
546 | 522 | db.metalock.Unlock() |
547 | 523 | |
548 | err := db.Do(func(t *Transaction) error { | |
524 | err := db.Do(func(t *RWTransaction) error { | |
549 | 525 | s.PageCount = int(t.meta.pgid) |
550 | 526 | s.FreePageCount = len(db.freelist.all()) |
551 | 527 | s.PageSize = db.pageSize |
555 | 531 | return nil, err |
556 | 532 | } |
557 | 533 | return s, nil |
558 | } | |
559 | ||
560 | // GoString returns the Go string representation of the database. | |
561 | func (db *DB) GoString() string { | |
562 | return fmt.Sprintf("bolt.DB{path:%q}", db.path) | |
563 | } | |
564 | ||
565 | // String returns the string representation of the database. | |
566 | func (db *DB) String() string { | |
567 | return fmt.Sprintf("DB<%q>", db.path) | |
568 | 534 | } |
569 | 535 | |
570 | 536 | // page retrieves a page reference from the mmap based on the current page size. |
187 | 187 | }) |
188 | 188 | } |
189 | 189 | |
190 | // Ensure that a Transaction can be retrieved. | |
191 | func TestDBRWTransaction(t *testing.T) { | |
192 | withOpenDB(func(db *DB, path string) { | |
193 | txn, err := db.RWTransaction() | |
194 | assert.NotNil(t, txn) | |
195 | assert.NoError(t, err) | |
196 | assert.Equal(t, txn.DB(), db) | |
197 | }) | |
198 | } | |
199 | ||
200 | // Ensure that opening a Transaction while the DB is closed returns an error. | |
201 | func TestRWTransactionOpenWithClosedDB(t *testing.T) { | |
202 | withDB(func(db *DB, path string) { | |
203 | txn, err := db.RWTransaction() | |
204 | assert.Equal(t, err, ErrDatabaseNotOpen) | |
205 | assert.Nil(t, txn) | |
206 | }) | |
207 | } | |
208 | ||
209 | 190 | // Ensure a database can provide a transactional block. |
210 | 191 | func TestDBTransactionBlock(t *testing.T) { |
211 | 192 | withOpenDB(func(db *DB, path string) { |
212 | err := db.Do(func(txn *Transaction) error { | |
193 | err := db.Do(func(txn *RWTransaction) error { | |
213 | 194 | txn.CreateBucket("widgets") |
214 | b := txn.Bucket("widgets") | |
215 | b.Put([]byte("foo"), []byte("bar")) | |
216 | b.Put([]byte("baz"), []byte("bat")) | |
217 | b.Delete([]byte("foo")) | |
195 | txn.Put("widgets", []byte("foo"), []byte("bar")) | |
196 | txn.Put("widgets", []byte("baz"), []byte("bat")) | |
197 | txn.Delete("widgets", []byte("foo")) | |
218 | 198 | return nil |
219 | 199 | }) |
220 | 200 | assert.NoError(t, err) |
228 | 208 | // Ensure a closed database returns an error while running a transaction block |
229 | 209 | func TestDBTransactionBlockWhileClosed(t *testing.T) { |
230 | 210 | withDB(func(db *DB, path string) { |
231 | err := db.Do(func(txn *Transaction) error { | |
211 | err := db.Do(func(txn *RWTransaction) error { | |
232 | 212 | txn.CreateBucket("widgets") |
233 | 213 | return nil |
234 | 214 | }) |
352 | 332 | // Ensure the database can return stats about itself. |
353 | 333 | func TestDBStat(t *testing.T) { |
354 | 334 | withOpenDB(func(db *DB, path string) { |
355 | db.Do(func(txn *Transaction) error { | |
335 | db.Do(func(txn *RWTransaction) error { | |
356 | 336 | txn.CreateBucket("widgets") |
357 | b := txn.Bucket("widgets") | |
358 | 337 | for i := 0; i < 10000; i++ { |
359 | b.Put([]byte(strconv.Itoa(i)), []byte(strconv.Itoa(i))) | |
338 | txn.Put("widgets", []byte(strconv.Itoa(i)), []byte(strconv.Itoa(i))) | |
360 | 339 | } |
361 | 340 | return nil |
362 | 341 | }) |
369 | 348 | t0, _ := db.Transaction() |
370 | 349 | t1, _ := db.Transaction() |
371 | 350 | t2, _ := db.Transaction() |
372 | t2.Rollback() | |
351 | t2.Close() | |
373 | 352 | |
374 | 353 | // Obtain stats. |
375 | 354 | stat, err := db.Stat() |
381 | 360 | assert.Equal(t, stat.TransactionCount, 2) |
382 | 361 | |
383 | 362 | // Close readers. |
384 | t0.Rollback() | |
385 | t1.Rollback() | |
363 | t0.Close() | |
364 | t1.Close() | |
386 | 365 | }) |
387 | 366 | } |
388 | 367 |
9 | 9 | system crash. Transactions which have not finished committing will simply be |
10 | 10 | rolled back in the event of a crash. |
11 | 11 | |
12 | The design of Bolt is based on Howard Chu's LMDB project. | |
12 | The design of Bolt is based on Howard Chu's LMDB database project. | |
13 | 13 | |
14 | 14 | Basics |
15 | 15 | |
16 | There are only a few types in Bolt: DB, Bucket, Transaction, and Cursor. The DB | |
17 | is a collection of buckets and is represented by a single file on disk. A | |
18 | bucket is a collection of unique keys that are associated with values. | |
16 | There are only a few types in Bolt: DB, Bucket, Transaction, RWTransaction, and | |
17 | Cursor. The DB is a collection of buckets and is represented by a single file | |
18 | on disk. A bucket is a collection of unique keys that are associated with values. | |
19 | 19 | |
20 | Transactions provide a consistent view of the database. They can be used for | |
21 | retrieving, setting, and deleting properties. They can also be used to iterate | |
22 | over all the values in a bucket. Only one writer Transaction can be in use at | |
23 | a time. | |
20 | Transactions provide read-only access to data inside the database. They can | |
21 | retrieve key/value pairs and can use Cursors to iterate over the entire dataset. | |
22 | RWTransactions provide read-write access to the database. They can create and | |
23 | delete buckets and they can insert and remove keys. Only one RWTransaction is | |
24 | allowed at a time. | |
24 | 25 | |
25 | 26 | |
26 | 27 | Caveats |
28 | 29 | The database uses a read-only, memory-mapped data file to ensure that |
29 | 30 | applications cannot corrupt the database, however, this means that keys and |
30 | 31 | values returned from Bolt cannot be changed. Writing to a read-only byte slice |
31 | will cause Go to panic. If you need to alter data returned from a Transaction | |
32 | you need to first copy it to a new byte slice. | |
32 | will cause Go to panic. If you need to work with data returned from a Get() you | |
33 | need to first copy it to a new byte slice. | |
33 | 34 | |
34 | 35 | Bolt currently works on Mac OS and Linux. Windows support is coming soon. |
35 | 36 |
14 | 14 | // ErrDatabaseOpen is returned when opening a database that is |
15 | 15 | // already open. |
16 | 16 | ErrDatabaseOpen = &Error{"database already open", nil} |
17 | ||
18 | // ErrTransactionNotWritable is returned changing data using a read-only | |
19 | // transaction. | |
20 | ErrTransactionNotWritable = &Error{"transaction not writable", nil} | |
21 | 17 | |
22 | 18 | // ErrBucketNotFound is returned when trying to access a bucket that has |
23 | 19 | // not been created yet. |
66 | 66 | defer db.Close() |
67 | 67 | |
68 | 68 | // Execute several commands within a write transaction. |
69 | err := db.Do(func(t *Transaction) error { | |
69 | err := db.Do(func(t *RWTransaction) error { | |
70 | 70 | if err := t.CreateBucket("widgets"); err != nil { |
71 | 71 | return err |
72 | 72 | } |
73 | ||
74 | b := t.Bucket("widgets") | |
75 | if err := b.Put([]byte("foo"), []byte("bar")); err != nil { | |
73 | if err := t.Put("widgets", []byte("foo"), []byte("bar")); err != nil { | |
76 | 74 | return err |
77 | 75 | } |
78 | 76 | return nil |
101 | 99 | |
102 | 100 | // Access data from within a read-only transactional block. |
103 | 101 | db.With(func(t *Transaction) error { |
104 | v := t.Bucket("people").Get([]byte("john")) | |
102 | v, _ := t.Get("people", []byte("john")) | |
105 | 103 | fmt.Printf("John's last name is %s.\n", string(v)) |
106 | 104 | return nil |
107 | 105 | }) |
134 | 132 | // A liger is awesome. |
135 | 133 | } |
136 | 134 | |
137 | func ExampleTransaction_Commit() { | |
138 | // Open the database. | |
139 | var db DB | |
140 | db.Open("/tmp/bolt/db_rwtransaction.db", 0666) | |
135 | func ExampleRWTransaction() { | |
136 | // Open the database. | |
137 | var db DB | |
138 | db.Open("/tmp/bolt/rwtransaction.db", 0666) | |
141 | 139 | defer db.Close() |
142 | 140 | |
143 | 141 | // Create a bucket. |
144 | 142 | db.CreateBucket("widgets") |
145 | 143 | |
146 | 144 | // Create several keys in a transaction. |
147 | txn, _ := db.RWTransaction() | |
148 | b := txn.Bucket("widgets") | |
149 | b.Put([]byte("john"), []byte("blue")) | |
150 | b.Put([]byte("abby"), []byte("red")) | |
151 | b.Put([]byte("zephyr"), []byte("purple")) | |
152 | txn.Commit() | |
145 | rwtxn, _ := db.RWTransaction() | |
146 | rwtxn.Put("widgets", []byte("john"), []byte("blue")) | |
147 | rwtxn.Put("widgets", []byte("abby"), []byte("red")) | |
148 | rwtxn.Put("widgets", []byte("zephyr"), []byte("purple")) | |
149 | rwtxn.Commit() | |
153 | 150 | |
154 | 151 | // Iterate over the values in sorted key order. |
155 | txn, _ = db.Transaction() | |
156 | c := txn.Bucket("widgets").Cursor() | |
152 | txn, _ := db.Transaction() | |
153 | c, _ := txn.Cursor("widgets") | |
157 | 154 | for k, v := c.First(); k != nil; k, v = c.Next() { |
158 | 155 | fmt.Printf("%s likes %s\n", string(k), string(v)) |
159 | 156 | } |
160 | txn.Rollback() | |
157 | txn.Close() | |
161 | 158 | |
162 | 159 | // Output: |
163 | 160 | // abby likes red |
165 | 162 | // zephyr likes purple |
166 | 163 | } |
167 | 164 | |
168 | func ExampleTransaction_Rollback() { | |
169 | // Open the database. | |
170 | var db DB | |
171 | db.Open("/tmp/bolt/transaction_close.db", 0666) | |
165 | func ExampleRWTransaction_rollback() { | |
166 | // Open the database. | |
167 | var db DB | |
168 | db.Open("/tmp/bolt/rwtransaction_rollback.db", 0666) | |
172 | 169 | defer db.Close() |
173 | 170 | |
174 | 171 | // Create a bucket. |
178 | 175 | db.Put("widgets", []byte("foo"), []byte("bar")) |
179 | 176 | |
180 | 177 | // Update the key but rollback the transaction so it never saves. |
181 | txn, _ := db.RWTransaction() | |
182 | txn.Bucket("widgets").Put([]byte("foo"), []byte("baz")) | |
183 | txn.Rollback() | |
178 | rwtxn, _ := db.RWTransaction() | |
179 | rwtxn.Put("widgets", []byte("foo"), []byte("baz")) | |
180 | rwtxn.Rollback() | |
184 | 181 | |
185 | 182 | // Ensure that our original value is still set. |
186 | 183 | value, _ := db.Get("widgets", []byte("foo")) |
55 | 55 | |
56 | 56 | // Verify all data is in for local data list. |
57 | 57 | for _, item := range local { |
58 | value := txn.Bucket("widgets").Get(item.Key) | |
58 | value, err := txn.Get("widgets", item.Key) | |
59 | 59 | if !assert.NoError(t, err) || !assert.Equal(t, value, item.Value) { |
60 | txn.Rollback() | |
60 | txn.Close() | |
61 | 61 | wg.Done() |
62 | 62 | t.FailNow() |
63 | 63 | } |
64 | 64 | } |
65 | 65 | |
66 | txn.Rollback() | |
66 | txn.Close() | |
67 | 67 | wg.Done() |
68 | 68 | <-readers |
69 | 69 | }() |
88 | 88 | } |
89 | 89 | |
90 | 90 | // Insert whole batch. |
91 | b := txn.Bucket("widgets") | |
92 | 91 | for _, item := range batchItems { |
93 | err := b.Put(item.Key, item.Value) | |
92 | err := txn.Put("widgets", item.Key, item.Value) | |
94 | 93 | if !assert.NoError(t, err) { |
95 | 94 | t.FailNow() |
96 | 95 | } |
7 | 7 | |
8 | 8 | // node represents an in-memory, deserialized page. |
9 | 9 | type node struct { |
10 | transaction *Transaction | |
10 | transaction *RWTransaction | |
11 | 11 | isLeaf bool |
12 | 12 | unbalanced bool |
13 | 13 | key []byte |
0 | package bolt | |
1 | ||
2 | import ( | |
3 | "sort" | |
4 | "unsafe" | |
5 | ) | |
6 | ||
7 | // RWTransaction represents a transaction that can read and write data. | |
8 | // Only one read/write transaction can be active for a database at a time. | |
9 | // RWTransaction is composed of a read-only Transaction so it can also use | |
10 | // functions provided by Transaction. | |
11 | type RWTransaction struct { | |
12 | Transaction | |
13 | nodes map[pgid]*node | |
14 | pending []*node | |
15 | } | |
16 | ||
17 | // init initializes the transaction. | |
18 | func (t *RWTransaction) init(db *DB) { | |
19 | t.Transaction.init(db) | |
20 | t.pages = make(map[pgid]*page) | |
21 | ||
22 | // Increment the transaction id. | |
23 | t.meta.txnid += txnid(1) | |
24 | } | |
25 | ||
26 | // CreateBucket creates a new bucket. | |
27 | // Returns an error if the bucket already exists, if the bucket name is blank, or if the bucket name is too long. | |
28 | func (t *RWTransaction) CreateBucket(name string) error { | |
29 | // Check if bucket already exists. | |
30 | if b := t.Bucket(name); b != nil { | |
31 | return ErrBucketExists | |
32 | } else if len(name) == 0 { | |
33 | return ErrBucketNameRequired | |
34 | } else if len(name) > MaxBucketNameSize { | |
35 | return ErrBucketNameTooLarge | |
36 | } | |
37 | ||
38 | // Create a blank root leaf page. | |
39 | p, err := t.allocate(1) | |
40 | if err != nil { | |
41 | return err | |
42 | } | |
43 | p.flags = leafPageFlag | |
44 | ||
45 | // Add bucket to buckets page. | |
46 | t.buckets.put(name, &bucket{root: p.id}) | |
47 | ||
48 | return nil | |
49 | } | |
50 | ||
51 | // CreateBucketIfNotExists creates a new bucket if it doesn't already exist. | |
52 | // Returns an error if the bucket name is blank, or if the bucket name is too long. | |
53 | func (t *RWTransaction) CreateBucketIfNotExists(name string) error { | |
54 | err := t.CreateBucket(name) | |
55 | if err != nil && err != ErrBucketExists { | |
56 | return err | |
57 | } | |
58 | return nil | |
59 | } | |
60 | ||
61 | // DeleteBucket deletes a bucket. | |
62 | // Returns an error if the bucket cannot be found. | |
63 | func (t *RWTransaction) DeleteBucket(name string) error { | |
64 | if b := t.Bucket(name); b == nil { | |
65 | return ErrBucketNotFound | |
66 | } | |
67 | ||
68 | // Remove from buckets page. | |
69 | t.buckets.del(name) | |
70 | ||
71 | // TODO(benbjohnson): Free all pages. | |
72 | ||
73 | return nil | |
74 | } | |
75 | ||
76 | // NextSequence returns an autoincrementing integer for the bucket. | |
77 | func (t *RWTransaction) NextSequence(name string) (int, error) { | |
78 | // Check if bucket already exists. | |
79 | b := t.Bucket(name) | |
80 | if b == nil { | |
81 | return 0, ErrBucketNotFound | |
82 | } | |
83 | ||
84 | // Make sure next sequence number will not be larger than the maximum | |
85 | // integer size of the system. | |
86 | if b.bucket.sequence == uint64(maxInt) { | |
87 | return 0, ErrSequenceOverflow | |
88 | } | |
89 | ||
90 | // Increment and return the sequence. | |
91 | b.bucket.sequence++ | |
92 | ||
93 | return int(b.bucket.sequence), nil | |
94 | } | |
95 | ||
96 | // Put sets the value for a key inside of the named bucket. | |
97 | // If the key exist then its previous value will be overwritten. | |
98 | // Returns an error if the bucket is not found, if the key is blank, if the key is too large, or if the value is too large. | |
99 | func (t *RWTransaction) Put(name string, key []byte, value []byte) error { | |
100 | b := t.Bucket(name) | |
101 | if b == nil { | |
102 | return ErrBucketNotFound | |
103 | } | |
104 | ||
105 | // Validate the key and data size. | |
106 | if len(key) == 0 { | |
107 | return ErrKeyRequired | |
108 | } else if len(key) > MaxKeySize { | |
109 | return ErrKeyTooLarge | |
110 | } else if len(value) > MaxValueSize { | |
111 | return ErrValueTooLarge | |
112 | } | |
113 | ||
114 | // Move cursor to correct position. | |
115 | c := b.cursor() | |
116 | c.Seek(key) | |
117 | ||
118 | // Insert the key/value. | |
119 | c.node(t).put(key, key, value, 0) | |
120 | ||
121 | return nil | |
122 | } | |
123 | ||
124 | // Delete removes a key from the named bucket. | |
125 | // If the key does not exist then nothing is done and a nil error is returned. | |
126 | // Returns an error if the bucket cannot be found. | |
127 | func (t *RWTransaction) Delete(name string, key []byte) error { | |
128 | b := t.Bucket(name) | |
129 | if b == nil { | |
130 | return ErrBucketNotFound | |
131 | } | |
132 | ||
133 | // Move cursor to correct position. | |
134 | c := b.cursor() | |
135 | c.Seek(key) | |
136 | ||
137 | // Delete the node if we have a matching key. | |
138 | c.node(t).del(key) | |
139 | ||
140 | return nil | |
141 | } | |
142 | ||
143 | // Commit writes all changes to disk and updates the meta page. | |
144 | // Returns an error if a disk write error occurs. | |
145 | func (t *RWTransaction) Commit() error { | |
146 | defer t.close() | |
147 | ||
148 | // TODO(benbjohnson): Use vectorized I/O to write out dirty pages. | |
149 | ||
150 | // Rebalance and spill data onto dirty pages. | |
151 | t.rebalance() | |
152 | t.spill() | |
153 | ||
154 | // Spill buckets page. | |
155 | p, err := t.allocate((t.buckets.size() / t.db.pageSize) + 1) | |
156 | if err != nil { | |
157 | return err | |
158 | } | |
159 | t.buckets.write(p) | |
160 | ||
161 | // Write dirty pages to disk. | |
162 | if err := t.write(); err != nil { | |
163 | return err | |
164 | } | |
165 | ||
166 | // Update the meta. | |
167 | t.meta.buckets = p.id | |
168 | ||
169 | // Write meta to disk. | |
170 | if err := t.writeMeta(); err != nil { | |
171 | return err | |
172 | } | |
173 | ||
174 | return nil | |
175 | } | |
176 | ||
177 | // Rollback closes the transaction and ignores all previous updates. | |
178 | func (t *RWTransaction) Rollback() { | |
179 | t.close() | |
180 | } | |
181 | ||
182 | func (t *RWTransaction) close() { | |
183 | t.db.rwlock.Unlock() | |
184 | } | |
185 | ||
186 | // allocate returns a contiguous block of memory starting at a given page. | |
187 | func (t *RWTransaction) allocate(count int) (*page, error) { | |
188 | p, err := t.db.allocate(count) | |
189 | if err != nil { | |
190 | return nil, err | |
191 | } | |
192 | ||
193 | // Save to our page cache. | |
194 | t.pages[p.id] = p | |
195 | ||
196 | return p, nil | |
197 | } | |
198 | ||
199 | // rebalance attempts to balance all nodes. | |
200 | func (t *RWTransaction) rebalance() { | |
201 | for _, n := range t.nodes { | |
202 | n.rebalance() | |
203 | } | |
204 | } | |
205 | ||
206 | // spill writes all the nodes to dirty pages. | |
207 | func (t *RWTransaction) spill() error { | |
208 | // Keep track of the current root nodes. | |
209 | // We will update this at the end once all nodes are created. | |
210 | type root struct { | |
211 | node *node | |
212 | pgid pgid | |
213 | } | |
214 | var roots []root | |
215 | ||
216 | // Sort nodes by highest depth first. | |
217 | nodes := make(nodesByDepth, 0, len(t.nodes)) | |
218 | for _, n := range t.nodes { | |
219 | nodes = append(nodes, n) | |
220 | } | |
221 | sort.Sort(nodes) | |
222 | ||
223 | // Spill nodes by deepest first. | |
224 | for i := 0; i < len(nodes); i++ { | |
225 | n := nodes[i] | |
226 | ||
227 | // Save existing root buckets for later. | |
228 | if n.parent == nil && n.pgid != 0 { | |
229 | roots = append(roots, root{n, n.pgid}) | |
230 | } | |
231 | ||
232 | // Split nodes into appropriate sized nodes. | |
233 | // The first node in this list will be a reference to n to preserve ancestry. | |
234 | newNodes := n.split(t.db.pageSize) | |
235 | t.pending = newNodes | |
236 | ||
237 | // If this is a root node that split then create a parent node. | |
238 | if n.parent == nil && len(newNodes) > 1 { | |
239 | n.parent = &node{transaction: t, isLeaf: false} | |
240 | nodes = append(nodes, n.parent) | |
241 | } | |
242 | ||
243 | // Add node's page to the freelist. | |
244 | if n.pgid > 0 { | |
245 | t.db.freelist.free(t.id(), t.page(n.pgid)) | |
246 | } | |
247 | ||
248 | // Write nodes to dirty pages. | |
249 | for i, newNode := range newNodes { | |
250 | // Allocate contiguous space for the node. | |
251 | p, err := t.allocate((newNode.size() / t.db.pageSize) + 1) | |
252 | if err != nil { | |
253 | return err | |
254 | } | |
255 | ||
256 | // Write the node to the page. | |
257 | newNode.write(p) | |
258 | newNode.pgid = p.id | |
259 | newNode.parent = n.parent | |
260 | ||
261 | // The first node should use the existing entry, other nodes are inserts. | |
262 | var oldKey []byte | |
263 | if i == 0 { | |
264 | oldKey = n.key | |
265 | } else { | |
266 | oldKey = newNode.inodes[0].key | |
267 | } | |
268 | ||
269 | // Update the parent entry. | |
270 | if newNode.parent != nil { | |
271 | newNode.parent.put(oldKey, newNode.inodes[0].key, nil, newNode.pgid) | |
272 | } | |
273 | } | |
274 | ||
275 | t.pending = nil | |
276 | } | |
277 | ||
278 | // Update roots with new roots. | |
279 | for _, root := range roots { | |
280 | t.buckets.updateRoot(root.pgid, root.node.root().pgid) | |
281 | } | |
282 | ||
283 | // Clear out nodes now that they are all spilled. | |
284 | t.nodes = make(map[pgid]*node) | |
285 | ||
286 | return nil | |
287 | } | |
288 | ||
289 | // write writes any dirty pages to disk. | |
290 | func (t *RWTransaction) write() error { | |
291 | // Sort pages by id. | |
292 | pages := make(pages, 0, len(t.pages)) | |
293 | for _, p := range t.pages { | |
294 | pages = append(pages, p) | |
295 | } | |
296 | sort.Sort(pages) | |
297 | ||
298 | // Write pages to disk in order. | |
299 | for _, p := range pages { | |
300 | size := (int(p.overflow) + 1) * t.db.pageSize | |
301 | buf := (*[maxAllocSize]byte)(unsafe.Pointer(p))[:size] | |
302 | offset := int64(p.id) * int64(t.db.pageSize) | |
303 | if _, err := t.db.file.WriteAt(buf, offset); err != nil { | |
304 | return err | |
305 | } | |
306 | } | |
307 | ||
308 | // Clear out page cache. | |
309 | t.pages = make(map[pgid]*page) | |
310 | ||
311 | return nil | |
312 | } | |
313 | ||
314 | // writeMeta writes the meta to the disk. | |
315 | func (t *RWTransaction) writeMeta() error { | |
316 | // Create a temporary buffer for the meta page. | |
317 | buf := make([]byte, t.db.pageSize) | |
318 | p := t.db.pageInBuffer(buf, 0) | |
319 | t.meta.write(p) | |
320 | ||
321 | // Write the meta page to file. | |
322 | t.db.metafile.WriteAt(buf, int64(p.id)*int64(t.db.pageSize)) | |
323 | ||
324 | return nil | |
325 | } | |
326 | ||
327 | // node creates a node from a page and associates it with a given parent. | |
328 | func (t *RWTransaction) node(pgid pgid, parent *node) *node { | |
329 | // Retrieve node if it has already been fetched. | |
330 | if n := t.nodes[pgid]; n != nil { | |
331 | return n | |
332 | } | |
333 | ||
334 | // Otherwise create a branch and cache it. | |
335 | n := &node{transaction: t, parent: parent} | |
336 | if n.parent != nil { | |
337 | n.depth = n.parent.depth + 1 | |
338 | } | |
339 | n.read(t.page(pgid)) | |
340 | t.nodes[pgid] = n | |
341 | ||
342 | return n | |
343 | } | |
344 | ||
345 | // dereference removes all references to the old mmap. | |
346 | func (t *RWTransaction) dereference() { | |
347 | for _, n := range t.nodes { | |
348 | n.dereference() | |
349 | } | |
350 | ||
351 | for _, n := range t.pending { | |
352 | n.dereference() | |
353 | } | |
354 | } |
0 | package bolt | |
1 | ||
2 | import ( | |
3 | "bytes" | |
4 | "fmt" | |
5 | "os" | |
6 | "strings" | |
7 | "testing" | |
8 | "testing/quick" | |
9 | ||
10 | "github.com/stretchr/testify/assert" | |
11 | ) | |
12 | ||
13 | // Ensure that a RWTransaction can be retrieved. | |
14 | func TestRWTransaction(t *testing.T) { | |
15 | withOpenDB(func(db *DB, path string) { | |
16 | txn, err := db.RWTransaction() | |
17 | assert.NotNil(t, txn) | |
18 | assert.NoError(t, err) | |
19 | assert.Equal(t, txn.DB(), db) | |
20 | }) | |
21 | } | |
22 | ||
23 | // Ensure that opening a RWTransaction while the DB is closed returns an error. | |
24 | func TestRWTransactionOpenWithClosedDB(t *testing.T) { | |
25 | withDB(func(db *DB, path string) { | |
26 | txn, err := db.RWTransaction() | |
27 | assert.Equal(t, err, ErrDatabaseNotOpen) | |
28 | assert.Nil(t, txn) | |
29 | }) | |
30 | } | |
31 | ||
32 | // Ensure that a bucket can be created and retrieved. | |
33 | func TestRWTransactionCreateBucket(t *testing.T) { | |
34 | withOpenDB(func(db *DB, path string) { | |
35 | // Create a bucket. | |
36 | err := db.CreateBucket("widgets") | |
37 | assert.NoError(t, err) | |
38 | ||
39 | // Read the bucket through a separate transaction. | |
40 | b, err := db.Bucket("widgets") | |
41 | assert.NotNil(t, b) | |
42 | assert.NoError(t, err) | |
43 | }) | |
44 | } | |
45 | ||
46 | // Ensure that a bucket can be created if it doesn't already exist. | |
47 | func TestRWTransactionCreateBucketIfNotExists(t *testing.T) { | |
48 | withOpenDB(func(db *DB, path string) { | |
49 | assert.NoError(t, db.CreateBucketIfNotExists("widgets")) | |
50 | assert.NoError(t, db.CreateBucketIfNotExists("widgets")) | |
51 | ||
52 | // Read the bucket through a separate transaction. | |
53 | b, err := db.Bucket("widgets") | |
54 | assert.NotNil(t, b) | |
55 | assert.NoError(t, err) | |
56 | }) | |
57 | } | |
58 | ||
59 | // Ensure that a bucket cannot be created twice. | |
60 | func TestRWTransactionRecreateBucket(t *testing.T) { | |
61 | withOpenDB(func(db *DB, path string) { | |
62 | // Create a bucket. | |
63 | err := db.CreateBucket("widgets") | |
64 | assert.NoError(t, err) | |
65 | ||
66 | // Create the same bucket again. | |
67 | err = db.CreateBucket("widgets") | |
68 | assert.Equal(t, err, ErrBucketExists) | |
69 | }) | |
70 | } | |
71 | ||
72 | // Ensure that a bucket is created with a non-blank name. | |
73 | func TestRWTransactionCreateBucketWithoutName(t *testing.T) { | |
74 | withOpenDB(func(db *DB, path string) { | |
75 | err := db.CreateBucket("") | |
76 | assert.Equal(t, err, ErrBucketNameRequired) | |
77 | }) | |
78 | } | |
79 | ||
80 | // Ensure that a bucket name is not too long. | |
81 | func TestRWTransactionCreateBucketWithLongName(t *testing.T) { | |
82 | withOpenDB(func(db *DB, path string) { | |
83 | err := db.CreateBucket(strings.Repeat("X", 255)) | |
84 | assert.NoError(t, err) | |
85 | ||
86 | err = db.CreateBucket(strings.Repeat("X", 256)) | |
87 | assert.Equal(t, err, ErrBucketNameTooLarge) | |
88 | }) | |
89 | } | |
90 | ||
91 | // Ensure that a bucket can be deleted. | |
92 | func TestRWTransactionDeleteBucket(t *testing.T) { | |
93 | withOpenDB(func(db *DB, path string) { | |
94 | // Create a bucket and add a value. | |
95 | db.CreateBucket("widgets") | |
96 | db.Put("widgets", []byte("foo"), []byte("bar")) | |
97 | ||
98 | // Delete the bucket and make sure we can't get the value. | |
99 | assert.NoError(t, db.DeleteBucket("widgets")) | |
100 | value, err := db.Get("widgets", []byte("foo")) | |
101 | assert.Equal(t, err, ErrBucketNotFound) | |
102 | assert.Nil(t, value) | |
103 | ||
104 | // Create the bucket again and make sure there's not a phantom value. | |
105 | assert.NoError(t, db.CreateBucket("widgets")) | |
106 | value, err = db.Get("widgets", []byte("foo")) | |
107 | assert.NoError(t, err) | |
108 | assert.Nil(t, value) | |
109 | }) | |
110 | } | |
111 | ||
112 | // Ensure that a bucket can return an autoincrementing sequence. | |
113 | func TestRWTransactionNextSequence(t *testing.T) { | |
114 | withOpenDB(func(db *DB, path string) { | |
115 | db.CreateBucket("widgets") | |
116 | db.CreateBucket("woojits") | |
117 | ||
118 | // Make sure sequence increments. | |
119 | seq, err := db.NextSequence("widgets") | |
120 | assert.NoError(t, err) | |
121 | assert.Equal(t, seq, 1) | |
122 | seq, err = db.NextSequence("widgets") | |
123 | assert.NoError(t, err) | |
124 | assert.Equal(t, seq, 2) | |
125 | ||
126 | // Buckets should be separate. | |
127 | seq, err = db.NextSequence("woojits") | |
128 | assert.NoError(t, err) | |
129 | assert.Equal(t, seq, 1) | |
130 | ||
131 | // Missing buckets return an error. | |
132 | seq, err = db.NextSequence("no_such_bucket") | |
133 | assert.Equal(t, err, ErrBucketNotFound) | |
134 | assert.Equal(t, seq, 0) | |
135 | }) | |
136 | } | |
137 | ||
138 | // Ensure that incrementing past the maximum sequence number will return an error. | |
139 | func TestRWTransactionNextSequenceOverflow(t *testing.T) { | |
140 | withOpenDB(func(db *DB, path string) { | |
141 | db.CreateBucket("widgets") | |
142 | db.Do(func(txn *RWTransaction) error { | |
143 | b := txn.Bucket("widgets") | |
144 | b.bucket.sequence = uint64(maxInt) | |
145 | seq, err := txn.NextSequence("widgets") | |
146 | assert.Equal(t, err, ErrSequenceOverflow) | |
147 | assert.Equal(t, seq, 0) | |
148 | return nil | |
149 | }) | |
150 | }) | |
151 | } | |
152 | ||
153 | // Ensure that an error is returned when inserting into a bucket that doesn't exist. | |
154 | func TestRWTransactionPutBucketNotFound(t *testing.T) { | |
155 | withOpenDB(func(db *DB, path string) { | |
156 | err := db.Put("widgets", []byte("foo"), []byte("bar")) | |
157 | assert.Equal(t, err, ErrBucketNotFound) | |
158 | }) | |
159 | } | |
160 | ||
161 | // Ensure that an error is returned when inserting with an empty key. | |
162 | func TestRWTransactionPutEmptyKey(t *testing.T) { | |
163 | withOpenDB(func(db *DB, path string) { | |
164 | db.CreateBucket("widgets") | |
165 | err := db.Put("widgets", []byte(""), []byte("bar")) | |
166 | assert.Equal(t, err, ErrKeyRequired) | |
167 | err = db.Put("widgets", nil, []byte("bar")) | |
168 | assert.Equal(t, err, ErrKeyRequired) | |
169 | }) | |
170 | } | |
171 | ||
172 | // Ensure that an error is returned when inserting with a key that's too large. | |
173 | func TestRWTransactionPutKeyTooLarge(t *testing.T) { | |
174 | withOpenDB(func(db *DB, path string) { | |
175 | db.CreateBucket("widgets") | |
176 | err := db.Put("widgets", make([]byte, 32769), []byte("bar")) | |
177 | assert.Equal(t, err, ErrKeyTooLarge) | |
178 | }) | |
179 | } | |
180 | ||
181 | // Ensure that an error is returned when deleting from a bucket that doesn't exist. | |
182 | func TestRWTransactionDeleteBucketNotFound(t *testing.T) { | |
183 | withOpenDB(func(db *DB, path string) { | |
184 | err := db.DeleteBucket("widgets") | |
185 | assert.Equal(t, err, ErrBucketNotFound) | |
186 | }) | |
187 | } | |
188 | ||
189 | // Ensure that a bucket can write random keys and values across multiple txns. | |
190 | func TestRWTransactionPutSingle(t *testing.T) { | |
191 | index := 0 | |
192 | f := func(items testdata) bool { | |
193 | withOpenDB(func(db *DB, path string) { | |
194 | m := make(map[string][]byte) | |
195 | ||
196 | db.CreateBucket("widgets") | |
197 | for _, item := range items { | |
198 | if err := db.Put("widgets", item.Key, item.Value); err != nil { | |
199 | panic("put error: " + err.Error()) | |
200 | } | |
201 | m[string(item.Key)] = item.Value | |
202 | ||
203 | // Verify all key/values so far. | |
204 | i := 0 | |
205 | for k, v := range m { | |
206 | value, err := db.Get("widgets", []byte(k)) | |
207 | if err != nil { | |
208 | panic("get error: " + err.Error()) | |
209 | } | |
210 | if !bytes.Equal(value, v) { | |
211 | db.CopyFile("/tmp/bolt.put.single.db", 0666) | |
212 | t.Fatalf("value mismatch [run %d] (%d of %d):\nkey: %x\ngot: %x\nexp: %x", index, i, len(m), []byte(k), value, v) | |
213 | } | |
214 | i++ | |
215 | } | |
216 | } | |
217 | ||
218 | fmt.Fprint(os.Stderr, ".") | |
219 | }) | |
220 | index++ | |
221 | return true | |
222 | } | |
223 | if err := quick.Check(f, qconfig()); err != nil { | |
224 | t.Error(err) | |
225 | } | |
226 | fmt.Fprint(os.Stderr, "\n") | |
227 | } | |
228 | ||
229 | // Ensure that a transaction can insert multiple key/value pairs at once. | |
230 | func TestRWTransactionPutMultiple(t *testing.T) { | |
231 | f := func(items testdata) bool { | |
232 | withOpenDB(func(db *DB, path string) { | |
233 | // Bulk insert all values. | |
234 | db.CreateBucket("widgets") | |
235 | rwtxn, _ := db.RWTransaction() | |
236 | for _, item := range items { | |
237 | assert.NoError(t, rwtxn.Put("widgets", item.Key, item.Value)) | |
238 | } | |
239 | assert.NoError(t, rwtxn.Commit()) | |
240 | ||
241 | // Verify all items exist. | |
242 | txn, _ := db.Transaction() | |
243 | for _, item := range items { | |
244 | value, err := txn.Get("widgets", item.Key) | |
245 | assert.NoError(t, err) | |
246 | if !assert.Equal(t, item.Value, value) { | |
247 | db.CopyFile("/tmp/bolt.put.multiple.db", 0666) | |
248 | t.FailNow() | |
249 | } | |
250 | } | |
251 | txn.Close() | |
252 | }) | |
253 | fmt.Fprint(os.Stderr, ".") | |
254 | return true | |
255 | } | |
256 | if err := quick.Check(f, qconfig()); err != nil { | |
257 | t.Error(err) | |
258 | } | |
259 | fmt.Fprint(os.Stderr, "\n") | |
260 | } | |
261 | ||
262 | // Ensure that a transaction can delete all key/value pairs and return to a single leaf page. | |
263 | func TestRWTransactionDelete(t *testing.T) { | |
264 | f := func(items testdata) bool { | |
265 | withOpenDB(func(db *DB, path string) { | |
266 | // Bulk insert all values. | |
267 | db.CreateBucket("widgets") | |
268 | rwtxn, _ := db.RWTransaction() | |
269 | for _, item := range items { | |
270 | assert.NoError(t, rwtxn.Put("widgets", item.Key, item.Value)) | |
271 | } | |
272 | assert.NoError(t, rwtxn.Commit()) | |
273 | ||
274 | // Remove items one at a time and check consistency. | |
275 | for i, item := range items { | |
276 | assert.NoError(t, db.Delete("widgets", item.Key)) | |
277 | ||
278 | // Anything before our deletion index should be nil. | |
279 | txn, _ := db.Transaction() | |
280 | for j, exp := range items { | |
281 | if j > i { | |
282 | value, err := txn.Get("widgets", exp.Key) | |
283 | assert.NoError(t, err) | |
284 | if !assert.Equal(t, exp.Value, value) { | |
285 | t.FailNow() | |
286 | } | |
287 | } else { | |
288 | value, err := txn.Get("widgets", exp.Key) | |
289 | assert.NoError(t, err) | |
290 | if !assert.Nil(t, value) { | |
291 | t.FailNow() | |
292 | } | |
293 | } | |
294 | } | |
295 | txn.Close() | |
296 | } | |
297 | }) | |
298 | fmt.Fprint(os.Stderr, ".") | |
299 | return true | |
300 | } | |
301 | if err := quick.Check(f, qconfig()); err != nil { | |
302 | t.Error(err) | |
303 | } | |
304 | fmt.Fprint(os.Stderr, "\n") | |
305 | } |
0 | 0 | package bolt |
1 | 1 | |
2 | 2 | import ( |
3 | "sort" | |
4 | "unsafe" | |
3 | "bytes" | |
5 | 4 | ) |
5 | ||
6 | // Transaction represents a read-only transaction on the database. | |
7 | // It can be used for retrieving values for keys as well as creating cursors for | |
8 | // iterating over the data. | |
9 | // | |
10 | // IMPORTANT: You must close transactions when you are done with them. Pages | |
11 | // can not be reclaimed by the writer until no more transactions are using them. | |
12 | // A long running read transaction can cause the database to quickly grow. | |
13 | type Transaction struct { | |
14 | db *DB | |
15 | meta *meta | |
16 | buckets *buckets | |
17 | pages map[pgid]*page | |
18 | } | |
6 | 19 | |
7 | 20 | // txnid represents the internal transaction identifier. |
8 | 21 | type txnid uint64 |
9 | 22 | |
10 | // Transaction represents a consistent view into the database. | |
11 | // Read-only transactions can be created by calling DB.Transaction(). | |
12 | // Read-write transactions can be created by calling DB.RWTransaction(). | |
13 | // Only one read-write transaction is allowed at a time. | |
14 | type Transaction struct { | |
15 | db *DB | |
16 | meta *meta | |
17 | buckets *buckets | |
18 | writable bool | |
19 | pages map[pgid]*page | |
20 | nodes map[pgid]*node | |
21 | pending []*node | |
22 | } | |
23 | ||
24 | // init initializes the transaction. | |
23 | // init initializes the transaction and associates it with a database. | |
25 | 24 | func (t *Transaction) init(db *DB) { |
26 | 25 | t.db = db |
27 | 26 | t.pages = nil |
33 | 32 | // Read in the buckets page. |
34 | 33 | t.buckets = &buckets{} |
35 | 34 | t.buckets.read(t.page(t.meta.buckets)) |
36 | ||
37 | t.pages = make(map[pgid]*page) | |
38 | ||
39 | // Increment the transaction id. | |
40 | t.meta.txnid += txnid(1) | |
41 | 35 | } |
42 | 36 | |
43 | 37 | // id returns the transaction id. |
45 | 39 | return t.meta.txnid |
46 | 40 | } |
47 | 41 | |
42 | // Close closes the transaction and releases any pages it is using. | |
43 | func (t *Transaction) Close() { | |
44 | t.db.removeTransaction(t) | |
45 | } | |
46 | ||
48 | 47 | // DB returns a reference to the database that created the transaction. |
49 | 48 | func (t *Transaction) DB() *DB { |
50 | 49 | return t.db |
51 | 50 | } |
52 | 51 | |
53 | // Writable returns whether the transaction can change data. | |
54 | func (t *Transaction) Writable() bool { | |
55 | return t.writable | |
56 | } | |
57 | ||
58 | 52 | // Bucket retrieves a bucket by name. |
59 | 53 | // Returns nil if the bucket does not exist. |
60 | 54 | func (t *Transaction) Bucket(name string) *Bucket { |
61 | _assert(t.isOpen(), "transaction not open") | |
62 | 55 | b := t.buckets.get(name) |
63 | 56 | if b == nil { |
64 | 57 | return nil |
73 | 66 | |
74 | 67 | // Buckets retrieves a list of all buckets. |
75 | 68 | func (t *Transaction) Buckets() []*Bucket { |
76 | _assert(t.isOpen(), "transaction not open") | |
77 | 69 | buckets := make([]*Bucket, 0, len(t.buckets.items)) |
78 | 70 | for name, b := range t.buckets.items { |
79 | 71 | bucket := &Bucket{bucket: b, transaction: t, name: name} |
82 | 74 | return buckets |
83 | 75 | } |
84 | 76 | |
85 | // CreateBucket creates a new bucket. | |
86 | // Returns an error if the transaction is read-only, if bucket already exists, | |
87 | // if the bucket name is blank, or if the bucket name is too long. | |
88 | func (t *Transaction) CreateBucket(name string) error { | |
89 | _assert(t.isOpen(), "transaction not open") | |
90 | if !t.writable { | |
91 | return ErrTransactionNotWritable | |
92 | } else if b := t.Bucket(name); b != nil { | |
93 | return ErrBucketExists | |
94 | } else if len(name) == 0 { | |
95 | return ErrBucketNameRequired | |
96 | } else if len(name) > MaxBucketNameSize { | |
97 | return ErrBucketNameTooLarge | |
77 | // Cursor creates a cursor associated with a given bucket. | |
78 | // The cursor is only valid as long as the Transaction is open. | |
79 | // Do not use a cursor after the transaction is closed. | |
80 | func (t *Transaction) Cursor(name string) (*Cursor, error) { | |
81 | b := t.Bucket(name) | |
82 | if b == nil { | |
83 | return nil, ErrBucketNotFound | |
98 | 84 | } |
85 | return b.cursor(), nil | |
86 | } | |
99 | 87 | |
100 | // Create a blank root leaf page. | |
101 | p, err := t.allocate(1) | |
88 | // Get retrieves the value for a key in a named bucket. | |
89 | // Returns a nil value if the key does not exist. | |
90 | // Returns an error if the bucket does not exist. | |
91 | func (t *Transaction) Get(name string, key []byte) (value []byte, err error) { | |
92 | c, err := t.Cursor(name) | |
93 | if err != nil { | |
94 | return nil, err | |
95 | } | |
96 | k, v := c.Seek(key) | |
97 | // If our target node isn't the same key as what's passed in then return nil. | |
98 | if !bytes.Equal(key, k) { | |
99 | return nil, nil | |
100 | } | |
101 | return v, nil | |
102 | } | |
103 | ||
104 | // ForEach executes a function for each key/value pair in a bucket. | |
105 | // An error is returned if the bucket cannot be found. | |
106 | func (t *Transaction) ForEach(name string, fn func(k, v []byte) error) error { | |
107 | // Open a cursor on the bucket. | |
108 | c, err := t.Cursor(name) | |
102 | 109 | if err != nil { |
103 | 110 | return err |
104 | 111 | } |
105 | p.flags = leafPageFlag | |
106 | 112 | |
107 | // Add bucket to buckets page. | |
108 | t.buckets.put(name, &bucket{root: p.id}) | |
109 | ||
110 | return nil | |
111 | } | |
112 | ||
113 | // CreateBucketIfNotExists creates a new bucket if it doesn't already exist. | |
114 | // Returns an error if the transaction is read-only, if the bucket name is | |
115 | // blank, or if the bucket name is too long. | |
116 | func (t *Transaction) CreateBucketIfNotExists(name string) error { | |
117 | _assert(t.isOpen(), "transaction not open") | |
118 | err := t.CreateBucket(name) | |
119 | if err != nil && err != ErrBucketExists { | |
120 | return err | |
121 | } | |
122 | return nil | |
123 | } | |
124 | ||
125 | // DeleteBucket deletes a bucket. | |
126 | // Returns an error if the transaction is read-only or if the bucket cannot be found. | |
127 | func (t *Transaction) DeleteBucket(name string) error { | |
128 | _assert(t.isOpen(), "transaction not open") | |
129 | if !t.writable { | |
130 | return ErrTransactionNotWritable | |
131 | } else if b := t.Bucket(name); b == nil { | |
132 | return ErrBucketNotFound | |
133 | } | |
134 | ||
135 | // Remove from buckets page. | |
136 | t.buckets.del(name) | |
137 | ||
138 | // TODO(benbjohnson): Free all pages. | |
139 | ||
140 | return nil | |
141 | } | |
142 | ||
143 | // Commit writes all changes to disk and updates the meta page. | |
144 | // Read-only transactions will simply be closed. | |
145 | // Returns an error if a disk write error occurs. | |
146 | func (t *Transaction) Commit() error { | |
147 | defer t.close() | |
148 | ||
149 | // Ignore commit for read-only transactions. | |
150 | if !t.writable { | |
151 | return nil | |
152 | } | |
153 | ||
154 | // TODO(benbjohnson): Use vectorized I/O to write out dirty pages. | |
155 | ||
156 | // Rebalance and spill data onto dirty pages. | |
157 | t.rebalance() | |
158 | t.spill() | |
159 | ||
160 | // Spill buckets page. | |
161 | p, err := t.allocate((t.buckets.size() / t.db.pageSize) + 1) | |
162 | if err != nil { | |
163 | return err | |
164 | } | |
165 | t.buckets.write(p) | |
166 | ||
167 | // Write dirty pages to disk. | |
168 | if err := t.write(); err != nil { | |
169 | return err | |
170 | } | |
171 | ||
172 | // Update the meta. | |
173 | t.meta.buckets = p.id | |
174 | ||
175 | // Write meta to disk. | |
176 | if err := t.writeMeta(); err != nil { | |
177 | return err | |
178 | } | |
179 | ||
180 | return nil | |
181 | } | |
182 | ||
183 | // Rollback closes the transaction and rolls back any pending changes. | |
184 | func (t *Transaction) Rollback() { | |
185 | t.close() | |
186 | } | |
187 | ||
188 | func (t *Transaction) close() { | |
189 | if t.writable { | |
190 | t.db.rwlock.Unlock() | |
191 | } else { | |
192 | t.db.removeTransaction(t) | |
193 | } | |
194 | ||
195 | // Detach from the database. | |
196 | t.db = nil | |
197 | } | |
198 | ||
199 | // isOpen returns whether the transaction is currently open. | |
200 | func (t *Transaction) isOpen() bool { | |
201 | return t.db != nil | |
202 | } | |
203 | ||
204 | // allocate returns a contiguous block of memory starting at a given page. | |
205 | func (t *Transaction) allocate(count int) (*page, error) { | |
206 | p, err := t.db.allocate(count) | |
207 | if err != nil { | |
208 | return nil, err | |
209 | } | |
210 | ||
211 | // Save to our page cache. | |
212 | t.pages[p.id] = p | |
213 | ||
214 | return p, nil | |
215 | } | |
216 | ||
217 | // rebalance attempts to balance all nodes. | |
218 | func (t *Transaction) rebalance() { | |
219 | for _, n := range t.nodes { | |
220 | n.rebalance() | |
221 | } | |
222 | } | |
223 | ||
224 | // spill writes all the nodes to dirty pages. | |
225 | func (t *Transaction) spill() error { | |
226 | // Keep track of the current root nodes. | |
227 | // We will update this at the end once all nodes are created. | |
228 | type root struct { | |
229 | node *node | |
230 | pgid pgid | |
231 | } | |
232 | var roots []root | |
233 | ||
234 | // Sort nodes by highest depth first. | |
235 | nodes := make(nodesByDepth, 0, len(t.nodes)) | |
236 | for _, n := range t.nodes { | |
237 | nodes = append(nodes, n) | |
238 | } | |
239 | sort.Sort(nodes) | |
240 | ||
241 | // Spill nodes by deepest first. | |
242 | for i := 0; i < len(nodes); i++ { | |
243 | n := nodes[i] | |
244 | ||
245 | // Save existing root buckets for later. | |
246 | if n.parent == nil && n.pgid != 0 { | |
247 | roots = append(roots, root{n, n.pgid}) | |
248 | } | |
249 | ||
250 | // Split nodes into appropriate sized nodes. | |
251 | // The first node in this list will be a reference to n to preserve ancestry. | |
252 | newNodes := n.split(t.db.pageSize) | |
253 | t.pending = newNodes | |
254 | ||
255 | // If this is a root node that split then create a parent node. | |
256 | if n.parent == nil && len(newNodes) > 1 { | |
257 | n.parent = &node{transaction: t, isLeaf: false} | |
258 | nodes = append(nodes, n.parent) | |
259 | } | |
260 | ||
261 | // Add node's page to the freelist. | |
262 | if n.pgid > 0 { | |
263 | t.db.freelist.free(t.id(), t.page(n.pgid)) | |
264 | } | |
265 | ||
266 | // Write nodes to dirty pages. | |
267 | for i, newNode := range newNodes { | |
268 | // Allocate contiguous space for the node. | |
269 | p, err := t.allocate((newNode.size() / t.db.pageSize) + 1) | |
270 | if err != nil { | |
271 | return err | |
272 | } | |
273 | ||
274 | // Write the node to the page. | |
275 | newNode.write(p) | |
276 | newNode.pgid = p.id | |
277 | newNode.parent = n.parent | |
278 | ||
279 | // The first node should use the existing entry, other nodes are inserts. | |
280 | var oldKey []byte | |
281 | if i == 0 { | |
282 | oldKey = n.key | |
283 | } else { | |
284 | oldKey = newNode.inodes[0].key | |
285 | } | |
286 | ||
287 | // Update the parent entry. | |
288 | if newNode.parent != nil { | |
289 | newNode.parent.put(oldKey, newNode.inodes[0].key, nil, newNode.pgid) | |
290 | } | |
291 | } | |
292 | ||
293 | t.pending = nil | |
294 | } | |
295 | ||
296 | // Update roots with new roots. | |
297 | for _, root := range roots { | |
298 | t.buckets.updateRoot(root.pgid, root.node.root().pgid) | |
299 | } | |
300 | ||
301 | // Clear out nodes now that they are all spilled. | |
302 | t.nodes = make(map[pgid]*node) | |
303 | ||
304 | return nil | |
305 | } | |
306 | ||
307 | // write writes any dirty pages to disk. | |
308 | func (t *Transaction) write() error { | |
309 | // Sort pages by id. | |
310 | pages := make(pages, 0, len(t.pages)) | |
311 | for _, p := range t.pages { | |
312 | pages = append(pages, p) | |
313 | } | |
314 | sort.Sort(pages) | |
315 | ||
316 | // Write pages to disk in order. | |
317 | for _, p := range pages { | |
318 | size := (int(p.overflow) + 1) * t.db.pageSize | |
319 | buf := (*[maxAllocSize]byte)(unsafe.Pointer(p))[:size] | |
320 | offset := int64(p.id) * int64(t.db.pageSize) | |
321 | if _, err := t.db.file.WriteAt(buf, offset); err != nil { | |
113 | // Iterate over each key/value pair in the bucket. | |
114 | for k, v := c.First(); k != nil; k, v = c.Next() { | |
115 | if err := fn(k, v); err != nil { | |
322 | 116 | return err |
323 | 117 | } |
324 | 118 | } |
325 | ||
326 | // Clear out page cache. | |
327 | t.pages = make(map[pgid]*page) | |
328 | ||
329 | return nil | |
330 | } | |
331 | ||
332 | // writeMeta writes the meta to the disk. | |
333 | func (t *Transaction) writeMeta() error { | |
334 | // Create a temporary buffer for the meta page. | |
335 | buf := make([]byte, t.db.pageSize) | |
336 | p := t.db.pageInBuffer(buf, 0) | |
337 | t.meta.write(p) | |
338 | ||
339 | // Write the meta page to file. | |
340 | t.db.metafile.WriteAt(buf, int64(p.id)*int64(t.db.pageSize)) | |
341 | 119 | |
342 | 120 | return nil |
343 | 121 | } |
356 | 134 | return t.db.page(id) |
357 | 135 | } |
358 | 136 | |
359 | // node creates a node from a page and associates it with a given parent. | |
360 | func (t *Transaction) node(pgid pgid, parent *node) *node { | |
361 | // Retrieve node if it has already been fetched. | |
362 | if n := t.nodes[pgid]; n != nil { | |
363 | return n | |
364 | } | |
365 | ||
366 | // Otherwise create a branch and cache it. | |
367 | n := &node{transaction: t, parent: parent} | |
368 | if n.parent != nil { | |
369 | n.depth = n.parent.depth + 1 | |
370 | } | |
371 | n.read(t.page(pgid)) | |
372 | t.nodes[pgid] = n | |
373 | ||
374 | return n | |
375 | } | |
376 | ||
377 | // dereference removes all references to the old mmap. | |
378 | func (t *Transaction) dereference() { | |
379 | for _, n := range t.nodes { | |
380 | n.dereference() | |
381 | } | |
382 | ||
383 | for _, n := range t.pending { | |
384 | n.dereference() | |
385 | } | |
386 | } | |
387 | ||
388 | 137 | // forEachPage iterates over every page within a given page and executes a function. |
389 | 138 | func (t *Transaction) forEachPage(pgid pgid, depth int, fn func(*page, int)) { |
390 | 139 | p := t.page(pgid) |
0 | 0 | package bolt |
1 | 1 | |
2 | 2 | import ( |
3 | "bytes" | |
4 | 3 | "fmt" |
5 | 4 | "os" |
6 | 5 | "sort" |
7 | "strings" | |
8 | 6 | "testing" |
9 | 7 | "testing/quick" |
10 | 8 | |
54 | 52 | withOpenDB(func(db *DB, path string) { |
55 | 53 | db.CreateBucket("widgets") |
56 | 54 | txn, _ := db.Transaction() |
57 | c := txn.Bucket("widgets").Cursor() | |
55 | c, err := txn.Cursor("widgets") | |
56 | assert.NoError(t, err) | |
58 | 57 | k, v := c.First() |
59 | 58 | assert.Nil(t, k) |
60 | 59 | assert.Nil(t, v) |
61 | txn.Rollback() | |
60 | txn.Close() | |
61 | }) | |
62 | } | |
63 | ||
64 | // Ensure that a Transaction returns a nil when a bucket doesn't exist. | |
65 | func TestTransactionCursorMissingBucket(t *testing.T) { | |
66 | withOpenDB(func(db *DB, path string) { | |
67 | db.CreateBucket("widgets") | |
68 | txn, _ := db.Transaction() | |
69 | c, err := txn.Cursor("woojits") | |
70 | assert.Nil(t, c) | |
71 | assert.Equal(t, err, ErrBucketNotFound) | |
72 | txn.Close() | |
62 | 73 | }) |
63 | 74 | } |
64 | 75 | |
70 | 81 | db.Put("widgets", []byte("foo"), []byte{0}) |
71 | 82 | db.Put("widgets", []byte("bar"), []byte{1}) |
72 | 83 | txn, _ := db.Transaction() |
73 | c := txn.Bucket("widgets").Cursor() | |
84 | c, err := txn.Cursor("widgets") | |
85 | assert.NoError(t, err) | |
74 | 86 | |
75 | 87 | k, v := c.First() |
76 | 88 | assert.Equal(t, string(k), "bar") |
92 | 104 | assert.Nil(t, k) |
93 | 105 | assert.Nil(t, v) |
94 | 106 | |
95 | txn.Rollback() | |
107 | txn.Close() | |
96 | 108 | }) |
97 | 109 | } |
98 | 110 | |
104 | 116 | db.Put("widgets", []byte("foo"), []byte{0}) |
105 | 117 | db.Put("widgets", []byte("bar"), []byte{1}) |
106 | 118 | txn, _ := db.Transaction() |
107 | c := txn.Bucket("widgets").Cursor() | |
119 | c, err := txn.Cursor("widgets") | |
120 | assert.NoError(t, err) | |
108 | 121 | |
109 | 122 | k, v := c.Last() |
110 | 123 | assert.Equal(t, string(k), "foo") |
126 | 139 | assert.Nil(t, k) |
127 | 140 | assert.Nil(t, v) |
128 | 141 | |
129 | txn.Rollback() | |
142 | txn.Close() | |
130 | 143 | }) |
131 | 144 | } |
132 | 145 | |
138 | 151 | db.Put("widgets", []byte("foo"), []byte{}) |
139 | 152 | |
140 | 153 | txn, _ := db.Transaction() |
141 | c := txn.Bucket("widgets").Cursor() | |
154 | c, err := txn.Cursor("widgets") | |
155 | assert.NoError(t, err) | |
142 | 156 | |
143 | 157 | k, _ := c.First() |
144 | 158 | assert.Equal(t, string(k), "bar") |
152 | 166 | k, _ = c.Next() |
153 | 167 | assert.Equal(t, string(k), "foo") |
154 | 168 | |
155 | txn.Rollback() | |
169 | txn.Close() | |
156 | 170 | }) |
157 | 171 | } |
158 | 172 | |
162 | 176 | withOpenDB(func(db *DB, path string) { |
163 | 177 | // Bulk insert all values. |
164 | 178 | db.CreateBucket("widgets") |
165 | txn, _ := db.RWTransaction() | |
166 | b := txn.Bucket("widgets") | |
179 | rwtxn, _ := db.RWTransaction() | |
167 | 180 | for _, item := range items { |
168 | assert.NoError(t, b.Put(item.Key, item.Value)) | |
169 | } | |
170 | assert.NoError(t, txn.Commit()) | |
181 | assert.NoError(t, rwtxn.Put("widgets", item.Key, item.Value)) | |
182 | } | |
183 | assert.NoError(t, rwtxn.Commit()) | |
171 | 184 | |
172 | 185 | // Sort test data. |
173 | 186 | sort.Sort(items) |
174 | 187 | |
175 | 188 | // Iterate over all items and check consistency. |
176 | 189 | var index = 0 |
177 | txn, _ = db.Transaction() | |
178 | c := txn.Bucket("widgets").Cursor() | |
190 | txn, _ := db.Transaction() | |
191 | c, err := txn.Cursor("widgets") | |
192 | assert.NoError(t, err) | |
179 | 193 | for k, v := c.First(); k != nil && index < len(items); k, v = c.Next() { |
180 | 194 | assert.Equal(t, k, items[index].Key) |
181 | 195 | assert.Equal(t, v, items[index].Value) |
182 | 196 | index++ |
183 | 197 | } |
184 | 198 | assert.Equal(t, len(items), index) |
185 | txn.Rollback() | |
199 | txn.Close() | |
186 | 200 | }) |
187 | 201 | fmt.Fprint(os.Stderr, ".") |
188 | 202 | return true |
199 | 213 | withOpenDB(func(db *DB, path string) { |
200 | 214 | // Bulk insert all values. |
201 | 215 | db.CreateBucket("widgets") |
202 | txn, _ := db.RWTransaction() | |
203 | b := txn.Bucket("widgets") | |
216 | rwtxn, _ := db.RWTransaction() | |
204 | 217 | for _, item := range items { |
205 | assert.NoError(t, b.Put(item.Key, item.Value)) | |
206 | } | |
207 | assert.NoError(t, txn.Commit()) | |
218 | assert.NoError(t, rwtxn.Put("widgets", item.Key, item.Value)) | |
219 | } | |
220 | assert.NoError(t, rwtxn.Commit()) | |
208 | 221 | |
209 | 222 | // Sort test data. |
210 | 223 | sort.Sort(revtestdata(items)) |
211 | 224 | |
212 | 225 | // Iterate over all items and check consistency. |
213 | 226 | var index = 0 |
214 | txn, _ = db.Transaction() | |
215 | c := txn.Bucket("widgets").Cursor() | |
227 | txn, _ := db.Transaction() | |
228 | c, err := txn.Cursor("widgets") | |
229 | assert.NoError(t, err) | |
216 | 230 | for k, v := c.Last(); k != nil && index < len(items); k, v = c.Prev() { |
217 | 231 | assert.Equal(t, k, items[index].Key) |
218 | 232 | assert.Equal(t, v, items[index].Value) |
219 | 233 | index++ |
220 | 234 | } |
221 | 235 | assert.Equal(t, len(items), index) |
222 | txn.Rollback() | |
236 | txn.Close() | |
223 | 237 | }) |
224 | 238 | fmt.Fprint(os.Stderr, ".") |
225 | 239 | return true |
229 | 243 | } |
230 | 244 | fmt.Fprint(os.Stderr, "\n") |
231 | 245 | } |
232 | ||
233 | // Ensure that a bucket can be created and retrieved. | |
234 | func TestTransactionCreateBucket(t *testing.T) { | |
235 | withOpenDB(func(db *DB, path string) { | |
236 | // Create a bucket. | |
237 | err := db.CreateBucket("widgets") | |
238 | assert.NoError(t, err) | |
239 | ||
240 | // Read the bucket through a separate transaction. | |
241 | b, err := db.Bucket("widgets") | |
242 | assert.NotNil(t, b) | |
243 | assert.NoError(t, err) | |
244 | }) | |
245 | } | |
246 | ||
247 | // Ensure that a bucket can be created if it doesn't already exist. | |
248 | func TestTransactionCreateBucketIfNotExists(t *testing.T) { | |
249 | withOpenDB(func(db *DB, path string) { | |
250 | assert.NoError(t, db.CreateBucketIfNotExists("widgets")) | |
251 | assert.NoError(t, db.CreateBucketIfNotExists("widgets")) | |
252 | ||
253 | // Read the bucket through a separate transaction. | |
254 | b, err := db.Bucket("widgets") | |
255 | assert.NotNil(t, b) | |
256 | assert.NoError(t, err) | |
257 | }) | |
258 | } | |
259 | ||
260 | // Ensure that a bucket cannot be created twice. | |
261 | func TestTransactionRecreateBucket(t *testing.T) { | |
262 | withOpenDB(func(db *DB, path string) { | |
263 | // Create a bucket. | |
264 | err := db.CreateBucket("widgets") | |
265 | assert.NoError(t, err) | |
266 | ||
267 | // Create the same bucket again. | |
268 | err = db.CreateBucket("widgets") | |
269 | assert.Equal(t, err, ErrBucketExists) | |
270 | }) | |
271 | } | |
272 | ||
273 | // Ensure that a bucket is created with a non-blank name. | |
274 | func TestTransactionCreateBucketWithoutName(t *testing.T) { | |
275 | withOpenDB(func(db *DB, path string) { | |
276 | err := db.CreateBucket("") | |
277 | assert.Equal(t, err, ErrBucketNameRequired) | |
278 | }) | |
279 | } | |
280 | ||
281 | // Ensure that a bucket name is not too long. | |
282 | func TestTransactionCreateBucketWithLongName(t *testing.T) { | |
283 | withOpenDB(func(db *DB, path string) { | |
284 | err := db.CreateBucket(strings.Repeat("X", 255)) | |
285 | assert.NoError(t, err) | |
286 | ||
287 | err = db.CreateBucket(strings.Repeat("X", 256)) | |
288 | assert.Equal(t, err, ErrBucketNameTooLarge) | |
289 | }) | |
290 | } | |
291 | ||
292 | // Ensure that a bucket can be deleted. | |
293 | func TestTransactionDeleteBucket(t *testing.T) { | |
294 | withOpenDB(func(db *DB, path string) { | |
295 | // Create a bucket and add a value. | |
296 | db.CreateBucket("widgets") | |
297 | db.Put("widgets", []byte("foo"), []byte("bar")) | |
298 | ||
299 | // Delete the bucket and make sure we can't get the value. | |
300 | assert.NoError(t, db.DeleteBucket("widgets")) | |
301 | value, err := db.Get("widgets", []byte("foo")) | |
302 | assert.Equal(t, err, ErrBucketNotFound) | |
303 | assert.Nil(t, value) | |
304 | ||
305 | // Create the bucket again and make sure there's not a phantom value. | |
306 | assert.NoError(t, db.CreateBucket("widgets")) | |
307 | value, err = db.Get("widgets", []byte("foo")) | |
308 | assert.NoError(t, err) | |
309 | assert.Nil(t, value) | |
310 | }) | |
311 | } | |
312 | ||
313 | // Ensure that a bucket can return an autoincrementing sequence. | |
314 | func TestTransactionNextSequence(t *testing.T) { | |
315 | withOpenDB(func(db *DB, path string) { | |
316 | db.CreateBucket("widgets") | |
317 | db.CreateBucket("woojits") | |
318 | ||
319 | // Make sure sequence increments. | |
320 | seq, err := db.NextSequence("widgets") | |
321 | assert.NoError(t, err) | |
322 | assert.Equal(t, seq, 1) | |
323 | seq, err = db.NextSequence("widgets") | |
324 | assert.NoError(t, err) | |
325 | assert.Equal(t, seq, 2) | |
326 | ||
327 | // Buckets should be separate. | |
328 | seq, err = db.NextSequence("woojits") | |
329 | assert.NoError(t, err) | |
330 | assert.Equal(t, seq, 1) | |
331 | ||
332 | // Missing buckets return an error. | |
333 | seq, err = db.NextSequence("no_such_bucket") | |
334 | assert.Equal(t, err, ErrBucketNotFound) | |
335 | assert.Equal(t, seq, 0) | |
336 | }) | |
337 | } | |
338 | ||
339 | // Ensure that incrementing past the maximum sequence number will return an error. | |
340 | func TestTransactionNextSequenceOverflow(t *testing.T) { | |
341 | withOpenDB(func(db *DB, path string) { | |
342 | db.CreateBucket("widgets") | |
343 | db.Do(func(txn *Transaction) error { | |
344 | b := txn.Bucket("widgets") | |
345 | b.bucket.sequence = uint64(maxInt) | |
346 | seq, err := b.NextSequence() | |
347 | assert.Equal(t, err, ErrSequenceOverflow) | |
348 | assert.Equal(t, seq, 0) | |
349 | return nil | |
350 | }) | |
351 | }) | |
352 | } | |
353 | ||
354 | // Ensure that an error is returned when inserting into a bucket that doesn't exist. | |
355 | func TestTransactionPutBucketNotFound(t *testing.T) { | |
356 | withOpenDB(func(db *DB, path string) { | |
357 | err := db.Put("widgets", []byte("foo"), []byte("bar")) | |
358 | assert.Equal(t, err, ErrBucketNotFound) | |
359 | }) | |
360 | } | |
361 | ||
362 | // Ensure that an error is returned when inserting with an empty key. | |
363 | func TestTransactionPutEmptyKey(t *testing.T) { | |
364 | withOpenDB(func(db *DB, path string) { | |
365 | db.CreateBucket("widgets") | |
366 | err := db.Put("widgets", []byte(""), []byte("bar")) | |
367 | assert.Equal(t, err, ErrKeyRequired) | |
368 | err = db.Put("widgets", nil, []byte("bar")) | |
369 | assert.Equal(t, err, ErrKeyRequired) | |
370 | }) | |
371 | } | |
372 | ||
373 | // Ensure that an error is returned when inserting with a key that's too large. | |
374 | func TestTransactionPutKeyTooLarge(t *testing.T) { | |
375 | withOpenDB(func(db *DB, path string) { | |
376 | db.CreateBucket("widgets") | |
377 | err := db.Put("widgets", make([]byte, 32769), []byte("bar")) | |
378 | assert.Equal(t, err, ErrKeyTooLarge) | |
379 | }) | |
380 | } | |
381 | ||
382 | // Ensure that an error is returned when deleting from a bucket that doesn't exist. | |
383 | func TestTransactionDeleteBucketNotFound(t *testing.T) { | |
384 | withOpenDB(func(db *DB, path string) { | |
385 | err := db.DeleteBucket("widgets") | |
386 | assert.Equal(t, err, ErrBucketNotFound) | |
387 | }) | |
388 | } | |
389 | ||
390 | // Ensure that a bucket can write random keys and values across multiple txns. | |
391 | func TestTransactionPutSingle(t *testing.T) { | |
392 | index := 0 | |
393 | f := func(items testdata) bool { | |
394 | withOpenDB(func(db *DB, path string) { | |
395 | m := make(map[string][]byte) | |
396 | ||
397 | db.CreateBucket("widgets") | |
398 | for _, item := range items { | |
399 | if err := db.Put("widgets", item.Key, item.Value); err != nil { | |
400 | panic("put error: " + err.Error()) | |
401 | } | |
402 | m[string(item.Key)] = item.Value | |
403 | ||
404 | // Verify all key/values so far. | |
405 | i := 0 | |
406 | for k, v := range m { | |
407 | value, err := db.Get("widgets", []byte(k)) | |
408 | if err != nil { | |
409 | panic("get error: " + err.Error()) | |
410 | } | |
411 | if !bytes.Equal(value, v) { | |
412 | db.CopyFile("/tmp/bolt.put.single.db", 0666) | |
413 | t.Fatalf("value mismatch [run %d] (%d of %d):\nkey: %x\ngot: %x\nexp: %x", index, i, len(m), []byte(k), value, v) | |
414 | } | |
415 | i++ | |
416 | } | |
417 | } | |
418 | ||
419 | fmt.Fprint(os.Stderr, ".") | |
420 | }) | |
421 | index++ | |
422 | return true | |
423 | } | |
424 | if err := quick.Check(f, qconfig()); err != nil { | |
425 | t.Error(err) | |
426 | } | |
427 | fmt.Fprint(os.Stderr, "\n") | |
428 | } | |
429 | ||
430 | // Ensure that a transaction can insert multiple key/value pairs at once. | |
431 | func TestTransactionPutMultiple(t *testing.T) { | |
432 | f := func(items testdata) bool { | |
433 | withOpenDB(func(db *DB, path string) { | |
434 | // Bulk insert all values. | |
435 | db.CreateBucket("widgets") | |
436 | txn, _ := db.RWTransaction() | |
437 | b := txn.Bucket("widgets") | |
438 | for _, item := range items { | |
439 | assert.NoError(t, b.Put(item.Key, item.Value)) | |
440 | } | |
441 | assert.NoError(t, txn.Commit()) | |
442 | ||
443 | // Verify all items exist. | |
444 | txn, _ = db.Transaction() | |
445 | for _, item := range items { | |
446 | value := txn.Bucket("widgets").Get(item.Key) | |
447 | if !assert.Equal(t, item.Value, value) { | |
448 | db.CopyFile("/tmp/bolt.put.multiple.db", 0666) | |
449 | t.FailNow() | |
450 | } | |
451 | } | |
452 | txn.Rollback() | |
453 | }) | |
454 | fmt.Fprint(os.Stderr, ".") | |
455 | return true | |
456 | } | |
457 | if err := quick.Check(f, qconfig()); err != nil { | |
458 | t.Error(err) | |
459 | } | |
460 | fmt.Fprint(os.Stderr, "\n") | |
461 | } | |
462 | ||
463 | // Ensure that a transaction can delete all key/value pairs and return to a single leaf page. | |
464 | func TestTransactionDelete(t *testing.T) { | |
465 | f := func(items testdata) bool { | |
466 | withOpenDB(func(db *DB, path string) { | |
467 | // Bulk insert all values. | |
468 | db.CreateBucket("widgets") | |
469 | txn, _ := db.RWTransaction() | |
470 | b := txn.Bucket("widgets") | |
471 | for _, item := range items { | |
472 | assert.NoError(t, b.Put(item.Key, item.Value)) | |
473 | } | |
474 | assert.NoError(t, txn.Commit()) | |
475 | ||
476 | // Remove items one at a time and check consistency. | |
477 | for i, item := range items { | |
478 | assert.NoError(t, db.Delete("widgets", item.Key)) | |
479 | ||
480 | // Anything before our deletion index should be nil. | |
481 | txn, _ := db.Transaction() | |
482 | for j, exp := range items { | |
483 | if j > i { | |
484 | value := txn.Bucket("widgets").Get(exp.Key) | |
485 | if !assert.Equal(t, exp.Value, value) { | |
486 | t.FailNow() | |
487 | } | |
488 | } else { | |
489 | value := txn.Bucket("widgets").Get(exp.Key) | |
490 | if !assert.Nil(t, value) { | |
491 | t.FailNow() | |
492 | } | |
493 | } | |
494 | } | |
495 | txn.Rollback() | |
496 | } | |
497 | }) | |
498 | fmt.Fprint(os.Stderr, ".") | |
499 | return true | |
500 | } | |
501 | if err := quick.Check(f, qconfig()); err != nil { | |
502 | t.Error(err) | |
503 | } | |
504 | fmt.Fprint(os.Stderr, "\n") | |
505 | } |