API Documentation.
Ben Johnson
10 years ago
0 | 0 | package bolt |
1 | 1 | |
2 | // Bucket represents a collection of key/value pairs inside the database. | |
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. | |
2 | 6 | type Bucket struct { |
3 | 7 | *bucket |
4 | 8 | name string |
5 | 9 | transaction *Transaction |
6 | 10 | } |
7 | 11 | |
12 | // bucket represents the on-file representation of a bucket. | |
8 | 13 | type bucket struct { |
9 | 14 | root pgid |
10 | 15 | } |
12 | 12 | |
13 | 13 | // size returns the size of the page after serialization. |
14 | 14 | func (b *buckets) size() int { |
15 | var size int = pageHeaderSize | |
16 | for key, _ := range b.items { | |
15 | var size = pageHeaderSize | |
16 | for key := range b.items { | |
17 | 17 | size += int(unsafe.Sizeof(bucket{})) + len(key) |
18 | 18 | } |
19 | 19 | return size |
69 | 69 | // write writes the items onto a page. |
70 | 70 | func (b *buckets) write(p *page) { |
71 | 71 | // Initialize page. |
72 | p.flags |= p_buckets | |
72 | p.flags |= bucketsPageFlag | |
73 | 73 | p.count = uint16(len(b.items)) |
74 | 74 | |
75 | 75 | // Sort keys. |
76 | 76 | var keys []string |
77 | for key, _ := range b.items { | |
77 | for key := range b.items { | |
78 | 78 | keys = append(keys, key) |
79 | 79 | } |
80 | 80 | sort.StringSlice(keys).Sort() |
2 | 2 | const version = 1 |
3 | 3 | |
4 | 4 | const ( |
5 | // MaxBucketNameSize is the maximum length of a bucket name, in bytes. | |
5 | 6 | MaxBucketNameSize = 255 |
6 | MaxKeySize = 32768 | |
7 | MaxDataSize = 4294967295 | |
7 | ||
8 | // MaxKeySize is the maximum length of a key, in bytes. | |
9 | MaxKeySize = 32768 | |
10 | ||
11 | // MaxValueSize is the maximum length of a value, in bytes. | |
12 | MaxValueSize = 4294967295 | |
8 | 13 | ) |
4 | 4 | "sort" |
5 | 5 | ) |
6 | 6 | |
7 | // Cursor represents an iterator that can traverse over all key/value pairs in a bucket in sorted order. | |
8 | // Cursors can be obtained from a Transaction and are valid as long as the Transaction is open. | |
7 | 9 | type Cursor struct { |
8 | 10 | transaction *Transaction |
9 | 11 | root pgid |
10 | 12 | stack []pageElementRef |
11 | 13 | } |
12 | 14 | |
13 | // First moves the cursor to the first item in the bucket and returns its key and data. | |
14 | func (c *Cursor) First() ([]byte, []byte) { | |
15 | // First moves the cursor to the first item in the bucket and returns its key and value. | |
16 | // If the bucket is empty then a nil key is returned. | |
17 | func (c *Cursor) First() (key []byte, value []byte) { | |
15 | 18 | if len(c.stack) > 0 { |
16 | 19 | c.stack = c.stack[:0] |
17 | 20 | } |
20 | 23 | return c.keyValue() |
21 | 24 | } |
22 | 25 | |
23 | // Move the cursor to the next key/value. | |
24 | func (c *Cursor) Next() ([]byte, []byte) { | |
26 | // Next moves the cursor to the next item in the bucket and returns its key and value. | |
27 | // If the cursor is at the end of the bucket then a nil key returned. | |
28 | func (c *Cursor) Next() (key []byte, value []byte) { | |
25 | 29 | // Attempt to move over one element until we're successful. |
26 | 30 | // Move up the stack as we hit the end of each page in our stack. |
27 | 31 | for i := len(c.stack) - 1; i >= 0; i-- { |
43 | 47 | return c.keyValue() |
44 | 48 | } |
45 | 49 | |
46 | // Get positions the cursor at a specific key and returns the its value. | |
47 | func (c *Cursor) Get(key []byte) []byte { | |
50 | // Get moves the cursor to a given key and returns its value. | |
51 | // If the key does not exist then the cursor is left at the closest key and a nil key is returned. | |
52 | func (c *Cursor) Get(key []byte) (value []byte) { | |
48 | 53 | // Start from root page and traverse to correct page. |
49 | 54 | c.stack = c.stack[:0] |
50 | 55 | c.search(key, c.transaction.page(c.root)) |
63 | 68 | return c.element().value() |
64 | 69 | } |
65 | 70 | |
66 | // first moves the cursor to the first leaf element under a page. | |
71 | // first moves the cursor to the first leaf element under the last page in the stack. | |
67 | 72 | func (c *Cursor) first() { |
68 | 73 | p := c.stack[len(c.stack)-1].page |
69 | 74 | for { |
70 | 75 | // Exit when we hit a leaf page. |
71 | if (p.flags & p_leaf) != 0 { | |
76 | if (p.flags & leafPageFlag) != 0 { | |
72 | 77 | break |
73 | 78 | } |
74 | 79 | |
78 | 83 | } |
79 | 84 | } |
80 | 85 | |
86 | // search recursively performs a binary search against a given page until it finds a given key. | |
81 | 87 | func (c *Cursor) search(key []byte, p *page) { |
82 | _assert((p.flags&(p_branch|p_leaf)) != 0, "invalid page type: "+p.typ()) | |
88 | _assert((p.flags&(branchPageFlag|leafPageFlag)) != 0, "invalid page type: "+p.typ()) | |
83 | 89 | e := pageElementRef{page: p} |
84 | 90 | c.stack = append(c.stack, e) |
85 | 91 | |
86 | 92 | // If we're on a leaf page then find the specific node. |
87 | if (p.flags & p_leaf) != 0 { | |
93 | if (p.flags & leafPageFlag) != 0 { | |
88 | 94 | c.nsearch(key, p) |
89 | 95 | return |
90 | 96 | } |
7 | 7 | "unsafe" |
8 | 8 | ) |
9 | 9 | |
10 | const ( | |
11 | db_nosync = iota | |
12 | db_nometasync | |
13 | ) | |
14 | ||
15 | const minPageSize = 0x1000 | |
16 | ||
10 | // The smallest size that the mmap can be. | |
17 | 11 | const minMmapSize = 1 << 22 // 4MB |
12 | ||
13 | // The largest step that can be taken when remapping the mmap. | |
18 | 14 | const maxMmapStep = 1 << 30 // 1GB |
19 | 15 | |
16 | // DB represents a collection of buckets persisted to a file on disk. | |
17 | // All data access is performed through transactions which can be obtained through the DB. | |
18 | // All the functions on DB will return a DatabaseNotOpenError if accessed before Open() is called. | |
20 | 19 | type DB struct { |
21 | 20 | os _os |
22 | 21 | syscall _syscall |
65 | 64 | |
66 | 65 | // Exit if the database is currently open. |
67 | 66 | if db.opened { |
68 | return DatabaseAlreadyOpenedError | |
67 | return DatabaseOpenError | |
69 | 68 | } |
70 | 69 | |
71 | 70 | // Open data file and separate sync handler for metadata writes. |
89 | 88 | } |
90 | 89 | } else { |
91 | 90 | // Read the first meta page to determine the page size. |
92 | var buf [minPageSize]byte | |
91 | var buf [0x1000]byte | |
93 | 92 | if _, err := db.file.ReadAt(buf[:], 0); err == nil { |
94 | 93 | m := db.pageInBuffer(buf[:], 0).meta() |
95 | 94 | if err := m.validate(); err != nil { |
201 | 200 | for i := 0; i < 2; i++ { |
202 | 201 | p := db.pageInBuffer(buf[:], pgid(i)) |
203 | 202 | p.id = pgid(i) |
204 | p.flags = p_meta | |
203 | p.flags = metaPageFlag | |
205 | 204 | |
206 | 205 | // Initialize the meta page. |
207 | 206 | m := p.meta() |
218 | 217 | // Write an empty freelist at page 3. |
219 | 218 | p := db.pageInBuffer(buf[:], pgid(2)) |
220 | 219 | p.id = pgid(2) |
221 | p.flags = p_freelist | |
220 | p.flags = freelistPageFlag | |
222 | 221 | p.count = 0 |
223 | 222 | |
224 | 223 | // Write an empty leaf page at page 4. |
225 | 224 | p = db.pageInBuffer(buf[:], pgid(3)) |
226 | 225 | p.id = pgid(3) |
227 | p.flags = p_buckets | |
226 | p.flags = bucketsPageFlag | |
228 | 227 | p.count = 0 |
229 | 228 | |
230 | 229 | // Write the buffer to our data file. |
235 | 234 | return nil |
236 | 235 | } |
237 | 236 | |
238 | // Close releases all resources related to the database. | |
237 | // Close releases all database resources. | |
238 | // All transactions must be closed before closing the database. | |
239 | 239 | func (db *DB) Close() { |
240 | 240 | db.metalock.Lock() |
241 | 241 | defer db.metalock.Unlock() |
249 | 249 | |
250 | 250 | // TODO(benbjohnson): Undo everything in Open(). |
251 | 251 | db.freelist = nil |
252 | db.path = "" | |
252 | 253 | |
253 | 254 | db.munmap() |
254 | 255 | } |
255 | 256 | |
256 | 257 | // Transaction creates a read-only transaction. |
257 | 258 | // Multiple read-only transactions can be used concurrently. |
259 | // | |
260 | // IMPORTANT: You must close the transaction after you are finished or else the database will not reclaim old pages. | |
258 | 261 | func (db *DB) Transaction() (*Transaction, error) { |
259 | 262 | db.metalock.Lock() |
260 | 263 | defer db.metalock.Unlock() |
281 | 284 | |
282 | 285 | // RWTransaction creates a read/write transaction. |
283 | 286 | // Only one read/write transaction is allowed at a time. |
287 | // You must call Commit() or Rollback() on the transaction to close it. | |
284 | 288 | func (db *DB) RWTransaction() (*RWTransaction, error) { |
285 | 289 | db.metalock.Lock() |
286 | 290 | defer db.metalock.Unlock() |
331 | 335 | } |
332 | 336 | |
333 | 337 | // Bucket retrieves a reference to a bucket. |
338 | // This is typically useful for checking the existence of a bucket. | |
334 | 339 | func (db *DB) Bucket(name string) (*Bucket, error) { |
335 | 340 | t, err := db.Transaction() |
336 | 341 | if err != nil { |
350 | 355 | return t.Buckets(), nil |
351 | 356 | } |
352 | 357 | |
353 | // CreateBucket creates a new bucket in the database. | |
358 | // CreateBucket creates a new bucket with the given name. | |
359 | // This function can return an error if the bucket already exists, if the name | |
360 | // is blank, or the bucket name is too long. | |
354 | 361 | func (db *DB) CreateBucket(name string) error { |
355 | 362 | t, err := db.RWTransaction() |
356 | 363 | if err != nil { |
366 | 373 | } |
367 | 374 | |
368 | 375 | // DeleteBucket removes a bucket from the database. |
376 | // Returns an error if the bucket does not exist. | |
369 | 377 | func (db *DB) DeleteBucket(name string) error { |
370 | 378 | t, err := db.RWTransaction() |
371 | 379 | if err != nil { |
381 | 389 | } |
382 | 390 | |
383 | 391 | // Get retrieves the value for a key in a bucket. |
392 | // Returns an error if the key does not exist. | |
384 | 393 | func (db *DB) Get(name string, key []byte) ([]byte, error) { |
385 | 394 | t, err := db.Transaction() |
386 | 395 | if err != nil { |
387 | 396 | return nil, err |
388 | 397 | } |
389 | 398 | defer t.Close() |
390 | return t.Get(name, key), nil | |
399 | return t.Get(name, key) | |
391 | 400 | } |
392 | 401 | |
393 | 402 | // Put sets the value for a key in a bucket. |
403 | // 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. | |
394 | 404 | func (db *DB) Put(name string, key []byte, value []byte) error { |
395 | 405 | t, err := db.RWTransaction() |
396 | 406 | if err != nil { |
404 | 414 | } |
405 | 415 | |
406 | 416 | // Delete removes a key from a bucket. |
417 | // Returns an error if the bucket cannot be found. | |
407 | 418 | func (db *DB) Delete(name string, key []byte) error { |
408 | 419 | t, err := db.RWTransaction() |
409 | 420 | if err != nil { |
417 | 428 | } |
418 | 429 | |
419 | 430 | // Copy writes the entire database to a writer. |
431 | // A reader transaction is maintained during the copy so it is safe to continue | |
432 | // using the database while a copy is in progress. | |
420 | 433 | func (db *DB) Copy(w io.Writer) error { |
421 | 434 | if !db.opened { |
422 | 435 | return DatabaseNotOpenError |
444 | 457 | } |
445 | 458 | |
446 | 459 | // CopyFile copies the entire database to file at the given path. |
460 | // A reader transaction is maintained during the copy so it is safe to continue | |
461 | // using the database while a copy is in progress. | |
447 | 462 | func (db *DB) CopyFile(path string) error { |
448 | 463 | f, err := os.Create(path) |
449 | 464 | if err != nil { |
502 | 517 | // sync flushes the file descriptor to disk. |
503 | 518 | func (db *DB) sync(force bool) error { |
504 | 519 | if db.opened { |
505 | return DatabaseAlreadyOpenedError | |
520 | return DatabaseNotOpenError | |
506 | 521 | } |
507 | 522 | if err := syscall.Fsync(int(db.file.Fd())); err != nil { |
508 | 523 | return err |
26 | 26 | withDB(func(db *DB, path string) { |
27 | 27 | db.Open(path, 0666) |
28 | 28 | err := db.Open(path, 0666) |
29 | assert.Equal(t, err, DatabaseAlreadyOpenedError) | |
29 | assert.Equal(t, err, DatabaseOpenError) | |
30 | 30 | }) |
31 | 31 | } |
32 | 32 |
0 | /* | |
1 | Package bolt implements a low-level key/value store in pure Go. It supports | |
2 | fully serializable transactions, ACID semantics, and lock-free MVCC with | |
3 | multiple readers and a single writer. Bolt can be used for projects that | |
4 | want a simple data store without the need to add large dependencies such as | |
5 | Postgres or MySQL. | |
6 | ||
7 | Bolt is a single-level, zero-copy, B+tree data store. This means that Bolt is | |
8 | optimized for fast read access and does not require recovery in the event of a | |
9 | system crash. Transactions which have not finished committing will simply be | |
10 | rolled back in the event of a crash. | |
11 | ||
12 | The design of Bolt is based on Howard Chu's LMDB database project. | |
13 | ||
14 | Basics | |
15 | ||
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 | ||
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. | |
25 | ||
26 | ||
27 | Caveats | |
28 | ||
29 | The database uses a read-only, memory-mapped data file to ensure that | |
30 | applications cannot corrupt the database, however, this means that keys and | |
31 | values returned from Bolt cannot be changed. Writing to a read-only 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. | |
34 | ||
35 | Bolt currently works on Mac OS and Linux. Windows support is coming soon. | |
36 | ||
37 | */ | |
0 | 38 | package bolt |
1 | ||
2 | // TODO(benbjohnson) |
0 | 0 | package bolt |
1 | 1 | |
2 | 2 | var ( |
3 | InvalidError = &Error{"Invalid database", nil} | |
4 | VersionMismatchError = &Error{"version mismatch", nil} | |
5 | DatabaseNotOpenError = &Error{"db is not open", nil} | |
6 | DatabaseAlreadyOpenedError = &Error{"db already open", nil} | |
7 | TransactionInProgressError = &Error{"writable transaction is already in progress", nil} | |
8 | InvalidTransactionError = &Error{"txn is invalid", nil} | |
9 | BucketAlreadyExistsError = &Error{"bucket already exists", nil} | |
3 | // InvalidError is returned when a data file is not a Bolt-formatted database. | |
4 | InvalidError = &Error{"Invalid database", nil} | |
5 | ||
6 | // VersionMismatchError is returned when the data file was created with a | |
7 | // different version of Bolt. | |
8 | VersionMismatchError = &Error{"version mismatch", nil} | |
9 | ||
10 | // DatabaseNotOpenError is returned when a DB instance is accessed before it | |
11 | // is opened or after it is closed. | |
12 | DatabaseNotOpenError = &Error{"database not open", nil} | |
13 | ||
14 | // DatabaseOpenError is returned when opening a database that is | |
15 | // already open. | |
16 | DatabaseOpenError = &Error{"database already open", nil} | |
17 | ||
18 | // BucketNotFoundError is returned when trying to access a bucket that has | |
19 | // not been created yet. | |
20 | BucketNotFoundError = &Error{"bucket not found", nil} | |
21 | ||
22 | // BucketExistsError is returned when creating a bucket that already exists. | |
23 | BucketExistsError = &Error{"bucket already exists", nil} | |
24 | ||
25 | // BucketNameRequiredError is returned when creating a bucket with a blank name. | |
26 | BucketNameRequiredError = &Error{"bucket name required", nil} | |
27 | ||
28 | // BucketNameTooLargeError is returned when creating a bucket with a name | |
29 | // that is longer than MaxBucketNameSize. | |
30 | BucketNameTooLargeError = &Error{"bucket name too large", nil} | |
31 | ||
32 | // KeyRequiredError is returned when inserting a zero-length key. | |
33 | KeyRequiredError = &Error{"key required", nil} | |
34 | ||
35 | // KeyTooLargeError is returned when inserting a key that is larger than MaxKeySize. | |
36 | KeyTooLargeError = &Error{"key too large", nil} | |
37 | ||
38 | // ValueTooLargeError is returned when inserting a value that is larger than MaxValueSize. | |
39 | ValueTooLargeError = &Error{"value too large", nil} | |
10 | 40 | ) |
11 | 41 | |
42 | // Error represents an error condition caused by Bolt. | |
12 | 43 | type Error struct { |
13 | 44 | message string |
14 | 45 | cause error |
15 | 46 | } |
16 | 47 | |
48 | // Error returns a string representation of the error. | |
17 | 49 | func (e *Error) Error() string { |
18 | 50 | if e.cause != nil { |
19 | 51 | return e.message + ": " + e.cause.Error() |
28 | 28 | // If a contiguous block cannot be found then 0 is returned. |
29 | 29 | func (f *freelist) allocate(n int) pgid { |
30 | 30 | var count int |
31 | var previd pgid = 0 | |
31 | var previd pgid | |
32 | 32 | for i, id := range f.ids { |
33 | 33 | // Reset count if this is not contiguous. |
34 | 34 | if previd == 0 || previd-id != 1 { |
81 | 81 | // become free. |
82 | 82 | func (f *freelist) write(p *page) { |
83 | 83 | ids := f.all() |
84 | p.flags |= p_freelist | |
84 | p.flags |= freelistPageFlag | |
85 | 85 | p.count = uint16(len(ids)) |
86 | 86 | copy(((*[maxAllocSize]pgid)(unsafe.Pointer(&p.ptr)))[:], ids) |
87 | 87 | } |
51 | 51 | // Create a page. |
52 | 52 | var buf [4096]byte |
53 | 53 | page := (*page)(unsafe.Pointer(&buf[0])) |
54 | page.flags = p_freelist | |
54 | page.flags = freelistPageFlag | |
55 | 55 | page.count = 2 |
56 | 56 | |
57 | 57 | // Insert 2 page ids. |
37 | 37 | func (m *meta) write(p *page) { |
38 | 38 | // Page id is either going to be 0 or 1 which we can determine by the Txn ID. |
39 | 39 | p.id = pgid(m.txnid % 2) |
40 | p.flags |= p_meta | |
40 | p.flags |= metaPageFlag | |
41 | 41 | |
42 | 42 | m.copy(p.meta()) |
43 | 43 | } |
27 | 27 | |
28 | 28 | // size returns the size of the node after serialization. |
29 | 29 | func (n *node) size() int { |
30 | var elementSize int = n.pageElementSize() | |
31 | ||
32 | var size int = pageHeaderSize | |
30 | var elementSize = n.pageElementSize() | |
31 | ||
32 | var size = pageHeaderSize | |
33 | 33 | for _, item := range n.inodes { |
34 | 34 | size += elementSize + len(item.key) + len(item.value) |
35 | 35 | } |
131 | 131 | // read initializes the node from a page. |
132 | 132 | func (n *node) read(p *page) { |
133 | 133 | n.pgid = p.id |
134 | n.isLeaf = ((p.flags & p_leaf) != 0) | |
134 | n.isLeaf = ((p.flags & leafPageFlag) != 0) | |
135 | 135 | n.inodes = make(inodes, int(p.count)) |
136 | 136 | |
137 | 137 | for i := 0; i < int(p.count); i++ { |
159 | 159 | func (n *node) write(p *page) { |
160 | 160 | // Initialize page. |
161 | 161 | if n.isLeaf { |
162 | p.flags |= p_leaf | |
162 | p.flags |= leafPageFlag | |
163 | 163 | } else { |
164 | p.flags |= p_branch | |
164 | p.flags |= branchPageFlag | |
165 | 165 | } |
166 | 166 | p.count = uint16(len(n.inodes)) |
167 | 167 | |
343 | 343 | copy(key, n.key) |
344 | 344 | n.key = key |
345 | 345 | |
346 | for i, _ := range n.inodes { | |
346 | for i := range n.inodes { | |
347 | 347 | inode := &n.inodes[i] |
348 | 348 | |
349 | 349 | key := make([]byte, len(inode.key)) |
27 | 27 | // Create a page. |
28 | 28 | var buf [4096]byte |
29 | 29 | page := (*page)(unsafe.Pointer(&buf[0])) |
30 | page.flags = p_leaf | |
30 | page.flags = leafPageFlag | |
31 | 31 | page.count = 2 |
32 | 32 | |
33 | 33 | // Insert 2 elements at the beginning. sizeof(leafPageElement) == 16 |
15 | 15 | const leafPageElementSize = int(unsafe.Sizeof(leafPageElement{})) |
16 | 16 | |
17 | 17 | const ( |
18 | p_branch = 0x01 | |
19 | p_leaf = 0x02 | |
20 | p_meta = 0x04 | |
21 | p_buckets = 0x08 | |
22 | p_freelist = 0x10 | |
18 | branchPageFlag = 0x01 | |
19 | leafPageFlag = 0x02 | |
20 | metaPageFlag = 0x04 | |
21 | bucketsPageFlag = 0x08 | |
22 | freelistPageFlag = 0x10 | |
23 | 23 | ) |
24 | 24 | |
25 | 25 | type pgid uint64 |
40 | 40 | |
41 | 41 | // typ returns a human readable page type string used for debugging. |
42 | 42 | func (p *page) typ() string { |
43 | if (p.flags & p_branch) != 0 { | |
43 | if (p.flags & branchPageFlag) != 0 { | |
44 | 44 | return "branch" |
45 | } else if (p.flags & p_leaf) != 0 { | |
45 | } else if (p.flags & leafPageFlag) != 0 { | |
46 | 46 | return "leaf" |
47 | } else if (p.flags & p_meta) != 0 { | |
47 | } else if (p.flags & metaPageFlag) != 0 { | |
48 | 48 | return "meta" |
49 | } else if (p.flags & p_buckets) != 0 { | |
49 | } else if (p.flags & bucketsPageFlag) != 0 { | |
50 | 50 | return "buckets" |
51 | } else if (p.flags & p_freelist) != 0 { | |
51 | } else if (p.flags & freelistPageFlag) != 0 { | |
52 | 52 | return "freelist" |
53 | 53 | } |
54 | 54 | return fmt.Sprintf("unknown<%02x>", p.flags) |
6 | 6 | |
7 | 7 | // Ensure that the page type can be returned in human readable format. |
8 | 8 | func TestPageTyp(t *testing.T) { |
9 | assert.Equal(t, (&page{flags: p_branch}).typ(), "branch") | |
10 | assert.Equal(t, (&page{flags: p_leaf}).typ(), "leaf") | |
11 | assert.Equal(t, (&page{flags: p_meta}).typ(), "meta") | |
12 | assert.Equal(t, (&page{flags: p_buckets}).typ(), "buckets") | |
13 | assert.Equal(t, (&page{flags: p_freelist}).typ(), "freelist") | |
9 | assert.Equal(t, (&page{flags: branchPageFlag}).typ(), "branch") | |
10 | assert.Equal(t, (&page{flags: leafPageFlag}).typ(), "leaf") | |
11 | assert.Equal(t, (&page{flags: metaPageFlag}).typ(), "meta") | |
12 | assert.Equal(t, (&page{flags: bucketsPageFlag}).typ(), "buckets") | |
13 | assert.Equal(t, (&page{flags: freelistPageFlag}).typ(), "freelist") | |
14 | 14 | assert.Equal(t, (&page{flags: 20000}).typ(), "unknown<4e20>") |
15 | 15 | } |
16 | 16 |
5 | 5 | ) |
6 | 6 | |
7 | 7 | // RWTransaction represents a transaction that can read and write data. |
8 | // Only one read/write transaction can be active for a DB at a time. | |
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. | |
9 | 11 | type RWTransaction struct { |
10 | 12 | Transaction |
11 | 13 | nodes map[pgid]*node |
24 | 26 | } |
25 | 27 | |
26 | 28 | // CreateBucket creates a new bucket. |
29 | // Returns an error if the bucket already exists, if the bucket name is blank, or if the bucket name is too long. | |
27 | 30 | func (t *RWTransaction) CreateBucket(name string) error { |
28 | 31 | // Check if bucket already exists. |
29 | 32 | if b := t.Bucket(name); b != nil { |
30 | return &Error{"bucket already exists", nil} | |
33 | return BucketExistsError | |
31 | 34 | } else if len(name) == 0 { |
32 | return &Error{"bucket name cannot be blank", nil} | |
35 | return BucketNameRequiredError | |
33 | 36 | } else if len(name) > MaxBucketNameSize { |
34 | return &Error{"bucket name too long", nil} | |
37 | return BucketNameTooLargeError | |
35 | 38 | } |
36 | 39 | |
37 | 40 | // Create a blank root leaf page. |
39 | 42 | if err != nil { |
40 | 43 | return err |
41 | 44 | } |
42 | p.flags = p_leaf | |
45 | p.flags = leafPageFlag | |
43 | 46 | |
44 | 47 | // Add bucket to buckets page. |
45 | 48 | t.buckets.put(name, &bucket{root: p.id}) |
47 | 50 | return nil |
48 | 51 | } |
49 | 52 | |
50 | // DropBucket deletes a bucket. | |
53 | // DeleteBucket deletes a bucket. | |
54 | // Returns an error if the bucket cannot be found. | |
51 | 55 | func (t *RWTransaction) DeleteBucket(name string) error { |
56 | if b := t.Bucket(name); b == nil { | |
57 | return BucketNotFoundError | |
58 | } | |
59 | ||
52 | 60 | // Remove from buckets page. |
53 | 61 | t.buckets.del(name) |
54 | 62 | |
55 | 63 | // TODO(benbjohnson): Free all pages. |
56 | return nil | |
57 | } | |
58 | ||
64 | ||
65 | return nil | |
66 | } | |
67 | ||
68 | // Put sets the value for a key inside of the named bucket. | |
69 | // If the key exist then its previous value will be overwritten. | |
70 | // 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. | |
59 | 71 | func (t *RWTransaction) Put(name string, key []byte, value []byte) error { |
60 | 72 | b := t.Bucket(name) |
61 | 73 | if b == nil { |
62 | return &Error{"bucket not found", nil} | |
74 | return BucketNotFoundError | |
63 | 75 | } |
64 | 76 | |
65 | 77 | // Validate the key and data size. |
66 | 78 | if len(key) == 0 { |
67 | return &Error{"key required", nil} | |
79 | return KeyRequiredError | |
68 | 80 | } else if len(key) > MaxKeySize { |
69 | return &Error{"key too large", nil} | |
70 | } else if len(value) > MaxDataSize { | |
71 | return &Error{"data too large", nil} | |
81 | return KeyTooLargeError | |
82 | } else if len(value) > MaxValueSize { | |
83 | return ValueTooLargeError | |
72 | 84 | } |
73 | 85 | |
74 | 86 | // Move cursor to correct position. |
81 | 93 | return nil |
82 | 94 | } |
83 | 95 | |
96 | // Delete removes a key from the named bucket. | |
97 | // If the key does not exist then nothing is done and a nil error is returned. | |
98 | // Returns an error if the bucket cannot be found. | |
84 | 99 | func (t *RWTransaction) Delete(name string, key []byte) error { |
85 | 100 | b := t.Bucket(name) |
86 | 101 | if b == nil { |
87 | return &Error{"bucket not found", nil} | |
102 | return BucketNotFoundError | |
88 | 103 | } |
89 | 104 | |
90 | 105 | // Move cursor to correct position. |
97 | 112 | return nil |
98 | 113 | } |
99 | 114 | |
100 | // Commit writes all changes to disk. | |
115 | // Commit writes all changes to disk and updates the meta page. | |
116 | // Returns an error if a disk write error occurs. | |
101 | 117 | func (t *RWTransaction) Commit() error { |
102 | 118 | defer t.close() |
103 | 119 | |
130 | 146 | return nil |
131 | 147 | } |
132 | 148 | |
149 | // Rollback closes the transaction and ignores all previous updates. | |
133 | 150 | func (t *RWTransaction) Rollback() { |
134 | 151 | t.close() |
135 | 152 | } |
43 | 43 | |
44 | 44 | // Create the same bucket again. |
45 | 45 | err = db.CreateBucket("widgets") |
46 | assert.Equal(t, err, &Error{"bucket already exists", nil}) | |
46 | assert.Equal(t, err, BucketExistsError) | |
47 | 47 | }) |
48 | 48 | } |
49 | 49 | |
51 | 51 | func TestRWTransactionCreateBucketWithoutName(t *testing.T) { |
52 | 52 | withOpenDB(func(db *DB, path string) { |
53 | 53 | err := db.CreateBucket("") |
54 | assert.Equal(t, err, &Error{"bucket name cannot be blank", nil}) | |
54 | assert.Equal(t, err, BucketNameRequiredError) | |
55 | 55 | }) |
56 | 56 | } |
57 | 57 | |
62 | 62 | assert.NoError(t, err) |
63 | 63 | |
64 | 64 | err = db.CreateBucket(strings.Repeat("X", 256)) |
65 | assert.Equal(t, err, &Error{"bucket name too long", nil}) | |
65 | assert.Equal(t, err, BucketNameTooLargeError) | |
66 | 66 | }) |
67 | 67 | } |
68 | 68 | |
151 | 151 | // Verify all items exist. |
152 | 152 | txn, _ := db.Transaction() |
153 | 153 | for _, item := range items { |
154 | if !assert.Equal(t, item.Value, txn.Get("widgets", item.Key)) { | |
154 | value, err := txn.Get("widgets", item.Key) | |
155 | assert.NoError(t, err) | |
156 | if !assert.Equal(t, item.Value, value) { | |
155 | 157 | db.CopyFile("/tmp/bolt.put.multiple.db") |
156 | 158 | t.FailNow() |
157 | 159 | } |
187 | 189 | txn, _ := db.Transaction() |
188 | 190 | for j, exp := range items { |
189 | 191 | if j > i { |
190 | if !assert.Equal(t, exp.Value, txn.Get("widgets", exp.Key)) { | |
192 | value, err := txn.Get("widgets", exp.Key) | |
193 | assert.NoError(t, err) | |
194 | if !assert.Equal(t, exp.Value, value) { | |
191 | 195 | t.FailNow() |
192 | 196 | } |
193 | 197 | } else { |
194 | if !assert.Nil(t, txn.Get("widgets", exp.Key)) { | |
198 | value, err := txn.Get("widgets", exp.Key) | |
199 | assert.NoError(t, err) | |
200 | if !assert.Nil(t, value) { | |
195 | 201 | t.FailNow() |
196 | 202 | } |
197 | 203 | } |
0 | 0 | package bolt |
1 | 1 | |
2 | const ( | |
3 | ps_modify = 1 | |
4 | ps_rootonly = 2 | |
5 | ps_first = 4 | |
6 | ps_last = 8 | |
7 | ) | |
8 | ||
9 | type txnid uint64 | |
10 | ||
2 | // Transaction represents a read-only transaction on the database. | |
3 | // It can be used for retrieving values for keys as well as creating cursors for | |
4 | // iterating over the data. | |
5 | // | |
6 | // IMPORTANT: You must close transactions when you are done with them. Pages | |
7 | // can not be reclaimed by the writer until no more transactions are using them. | |
8 | // A long running read transaction can cause the database to quickly grow. | |
11 | 9 | type Transaction struct { |
12 | 10 | db *DB |
13 | 11 | meta *meta |
14 | 12 | buckets *buckets |
15 | 13 | pages map[pgid]*page |
16 | 14 | } |
15 | ||
16 | // txnid represents the internal transaction identifier. | |
17 | type txnid uint64 | |
17 | 18 | |
18 | 19 | // init initializes the transaction and associates it with a database. |
19 | 20 | func (t *Transaction) init(db *DB) { |
30 | 31 | return t.meta.txnid |
31 | 32 | } |
32 | 33 | |
34 | // Close closes the transaction and releases any pages it is using. | |
33 | 35 | func (t *Transaction) Close() { |
34 | 36 | t.db.removeTransaction(t) |
35 | 37 | } |
36 | 38 | |
39 | // DB returns a reference to the database that created the transaction. | |
37 | 40 | func (t *Transaction) DB() *DB { |
38 | 41 | return t.db |
39 | 42 | } |
40 | 43 | |
41 | 44 | // Bucket retrieves a bucket by name. |
45 | // Returns nil if the bucket does not exist. | |
42 | 46 | func (t *Transaction) Bucket(name string) *Bucket { |
43 | 47 | b := t.buckets.get(name) |
44 | 48 | if b == nil { |
59 | 63 | } |
60 | 64 | |
61 | 65 | // Cursor creates a cursor associated with a given bucket. |
62 | func (t *Transaction) Cursor(name string) *Cursor { | |
66 | // The cursor is only valid as long as the Transaction is open. | |
67 | // Do not use a cursor after the transaction is closed. | |
68 | func (t *Transaction) Cursor(name string) (*Cursor, error) { | |
63 | 69 | b := t.Bucket(name) |
64 | 70 | if b == nil { |
65 | return nil | |
71 | return nil, BucketNotFoundError | |
66 | 72 | } |
67 | return b.cursor() | |
73 | return b.cursor(), nil | |
68 | 74 | } |
69 | 75 | |
70 | 76 | // Get retrieves the value for a key in a named bucket. |
71 | func (t *Transaction) Get(name string, key []byte) []byte { | |
72 | c := t.Cursor(name) | |
73 | if c == nil { | |
74 | return nil | |
77 | // Returns a nil value if the key does not exist. | |
78 | // Returns an error if the bucket does not exist. | |
79 | func (t *Transaction) Get(name string, key []byte) (value []byte, err error) { | |
80 | c, err := t.Cursor(name) | |
81 | if err != nil { | |
82 | return nil, err | |
75 | 83 | } |
76 | return c.Get(key) | |
84 | return c.Get(key), nil | |
77 | 85 | } |
78 | 86 | |
79 | 87 | // page returns a reference to the page with a given id. |
42 | 42 | withOpenDB(func(db *DB, path string) { |
43 | 43 | db.CreateBucket("widgets") |
44 | 44 | txn, _ := db.Transaction() |
45 | c := txn.Cursor("widgets") | |
45 | c, err := txn.Cursor("widgets") | |
46 | assert.NoError(t, err) | |
46 | 47 | k, v := c.First() |
47 | 48 | assert.Nil(t, k) |
48 | 49 | assert.Nil(t, v) |
55 | 56 | withOpenDB(func(db *DB, path string) { |
56 | 57 | db.CreateBucket("widgets") |
57 | 58 | txn, _ := db.Transaction() |
58 | assert.Nil(t, txn.Cursor("woojits")) | |
59 | c, err := txn.Cursor("woojits") | |
60 | assert.Nil(t, c) | |
61 | assert.Equal(t, err, BucketNotFoundError) | |
59 | 62 | txn.Close() |
60 | 63 | }) |
61 | 64 | } |
68 | 71 | db.Put("widgets", []byte("foo"), []byte{0}) |
69 | 72 | db.Put("widgets", []byte("bar"), []byte{1}) |
70 | 73 | txn, _ := db.Transaction() |
71 | c := txn.Cursor("widgets") | |
74 | c, err := txn.Cursor("widgets") | |
75 | assert.NoError(t, err) | |
72 | 76 | |
73 | 77 | k, v := c.First() |
74 | 78 | assert.Equal(t, string(k), "bar") |
102 | 106 | db.Put("widgets", []byte("foo"), []byte{}) |
103 | 107 | |
104 | 108 | txn, _ := db.Transaction() |
105 | c := txn.Cursor("widgets") | |
109 | c, err := txn.Cursor("widgets") | |
110 | assert.NoError(t, err) | |
106 | 111 | |
107 | 112 | k, _ := c.First() |
108 | 113 | assert.Equal(t, string(k), "bar") |
138 | 143 | // Iterate over all items and check consistency. |
139 | 144 | var index = 0 |
140 | 145 | txn, _ := db.Transaction() |
141 | c := txn.Cursor("widgets") | |
146 | c, err := txn.Cursor("widgets") | |
147 | assert.NoError(t, err) | |
142 | 148 | for k, v := c.First(); k != nil && index < len(items); k, v = c.Next() { |
143 | 149 | assert.Equal(t, k, items[index].Key) |
144 | 150 | assert.Equal(t, v, items[index].Value) |