Imported Upstream version 1.2.1
aviau
7 years ago
0 | Bolt [![Build Status](https://drone.io/github.com/boltdb/bolt/status.png)](https://drone.io/github.com/boltdb/bolt/latest) [![Coverage Status](https://coveralls.io/repos/boltdb/bolt/badge.svg?branch=master)](https://coveralls.io/r/boltdb/bolt?branch=master) [![GoDoc](https://godoc.org/github.com/boltdb/bolt?status.svg)](https://godoc.org/github.com/boltdb/bolt) ![Version](https://img.shields.io/badge/version-1.0-green.svg) | |
0 | Bolt [![Coverage Status](https://coveralls.io/repos/boltdb/bolt/badge.svg?branch=master)](https://coveralls.io/r/boltdb/bolt?branch=master) [![GoDoc](https://godoc.org/github.com/boltdb/bolt?status.svg)](https://godoc.org/github.com/boltdb/bolt) ![Version](https://img.shields.io/badge/version-1.0-green.svg) | |
1 | 1 | ==== |
2 | 2 | |
3 | 3 | Bolt is a pure Go key/value store inspired by [Howard Chu's][hyc_symas] |
426 | 426 | }) |
427 | 427 | ``` |
428 | 428 | |
429 | Note that, while RFC3339 is sortable, the Golang implementation of RFC3339Nano does not use a fixed number of digits after the decimal point and is therefore not sortable. | |
430 | ||
429 | 431 | |
430 | 432 | #### ForEach() |
431 | 433 | |
436 | 438 | db.View(func(tx *bolt.Tx) error { |
437 | 439 | // Assume bucket exists and has keys |
438 | 440 | b := tx.Bucket([]byte("MyBucket")) |
439 | ||
441 | ||
440 | 442 | b.ForEach(func(k, v []byte) error { |
441 | 443 | fmt.Printf("key=%s, value=%s\n", k, v) |
442 | 444 | return nil |
616 | 618 | { |
617 | 619 | NSURL* URL= [NSURL fileURLWithPath: filePathString]; |
618 | 620 | assert([[NSFileManager defaultManager] fileExistsAtPath: [URL path]]); |
619 | ||
621 | ||
620 | 622 | NSError *error = nil; |
621 | 623 | BOOL success = [URL setResourceValue: [NSNumber numberWithBool: YES] |
622 | 624 | forKey: NSURLIsExcludedFromBackupKey error: &error]; |
839 | 841 | * [Go Report Card](https://goreportcard.com/) - Go code quality report cards as a (free and open source) service. |
840 | 842 | * [Boltdb Boilerplate](https://github.com/bobintornado/boltdb-boilerplate) - Boilerplate wrapper around bolt aiming to make simple calls one-liners. |
841 | 843 | * [lru](https://github.com/crowdriff/lru) - Easy to use Bolt-backed Least-Recently-Used (LRU) read-through cache with chainable remote stores. |
844 | * [Storm](https://github.com/asdine/storm) - A simple ORM around BoltDB. | |
845 | * [GoWebApp](https://github.com/josephspurrier/gowebapp) - A basic MVC web application in Go using BoltDB. | |
846 | * [SimpleBolt](https://github.com/xyproto/simplebolt) - A simple way to use BoltDB. Deals mainly with strings. | |
847 | * [Algernon](https://github.com/xyproto/algernon) - A HTTP/2 web server with built-in support for Lua. Uses BoltDB as the default database backend. | |
842 | 848 | |
843 | 849 | If you are using Bolt in a project please send a pull request to add it to the list. |
73 | 73 | if tx.Bucket([]byte("widgets")).Get([]byte("foo")) != nil { |
74 | 74 | t.Fatal("expected nil value") |
75 | 75 | } |
76 | return nil | |
77 | }); err != nil { | |
78 | t.Fatal(err) | |
79 | } | |
80 | } | |
81 | ||
82 | // Ensure that a slice returned from a bucket has a capacity equal to its length. | |
83 | // This also allows slices to be appended to since it will require a realloc by Go. | |
84 | // | |
85 | // https://github.com/boltdb/bolt/issues/544 | |
86 | func TestBucket_Get_Capacity(t *testing.T) { | |
87 | db := MustOpenDB() | |
88 | defer db.MustClose() | |
89 | ||
90 | // Write key to a bucket. | |
91 | if err := db.Update(func(tx *bolt.Tx) error { | |
92 | b, err := tx.CreateBucket([]byte("bucket")) | |
93 | if err != nil { | |
94 | return err | |
95 | } | |
96 | return b.Put([]byte("key"), []byte("val")) | |
97 | }); err != nil { | |
98 | t.Fatal(err) | |
99 | } | |
100 | ||
101 | // Retrieve value and attempt to append to it. | |
102 | if err := db.Update(func(tx *bolt.Tx) error { | |
103 | k, v := tx.Bucket([]byte("bucket")).Cursor().First() | |
104 | ||
105 | // Verify capacity. | |
106 | if len(k) != cap(k) { | |
107 | t.Fatalf("unexpected key slice capacity: %d", cap(k)) | |
108 | } else if len(v) != cap(v) { | |
109 | t.Fatalf("unexpected value slice capacity: %d", cap(v)) | |
110 | } | |
111 | ||
112 | // Ensure slice can be appended to without a segfault. | |
113 | k = append(k, []byte("123")...) | |
114 | v = append(v, []byte("123")...) | |
115 | ||
76 | 116 | return nil |
77 | 117 | }); err != nil { |
78 | 118 | t.Fatal(err) |
35 | 35 | DefaultAllocSize = 16 * 1024 * 1024 |
36 | 36 | ) |
37 | 37 | |
38 | // default page size for db is set to the OS page size. | |
39 | var defaultPageSize = os.Getpagesize() | |
40 | ||
38 | 41 | // DB represents a collection of buckets persisted to a file on disk. |
39 | 42 | // All data access is performed through transactions which can be obtained through the DB. |
40 | 43 | // All the functions on DB will return a ErrDatabaseNotOpen if accessed before Open() is called. |
93 | 96 | path string |
94 | 97 | file *os.File |
95 | 98 | lockfile *os.File // windows only |
96 | dataref []byte // mmap'ed readonly, write throws SEGV | |
99 | dataref []byte // mmap'ed readonly, write throws SEGV | |
97 | 100 | data *[maxMapSize]byte |
98 | 101 | datasz int |
99 | 102 | filesz int // current on disk file size |
106 | 109 | freelist *freelist |
107 | 110 | stats Stats |
108 | 111 | |
112 | pagePool sync.Pool | |
113 | ||
109 | 114 | batchMu sync.Mutex |
110 | 115 | batch *batch |
111 | 116 | |
199 | 204 | if _, err := db.file.ReadAt(buf[:], 0); err == nil { |
200 | 205 | m := db.pageInBuffer(buf[:], 0).meta() |
201 | 206 | if err := m.validate(); err != nil { |
202 | return nil, err | |
207 | // If we can't read the page size, we can assume it's the same | |
208 | // as the OS -- since that's how the page size was chosen in the | |
209 | // first place. | |
210 | // | |
211 | // If the first page is invalid and this OS uses a different | |
212 | // page size than what the database was created with then we | |
213 | // are out of luck and cannot access the database. | |
214 | db.pageSize = os.Getpagesize() | |
215 | } else { | |
216 | db.pageSize = int(m.pageSize) | |
203 | 217 | } |
204 | db.pageSize = int(m.pageSize) | |
205 | } | |
218 | } | |
219 | } | |
220 | ||
221 | // Initialize page pool. | |
222 | db.pagePool = sync.Pool{ | |
223 | New: func() interface{} { | |
224 | return make([]byte, db.pageSize) | |
225 | }, | |
206 | 226 | } |
207 | 227 | |
208 | 228 | // Memory map the data file. |
261 | 281 | db.meta0 = db.page(0).meta() |
262 | 282 | db.meta1 = db.page(1).meta() |
263 | 283 | |
264 | // Validate the meta pages. | |
265 | if err := db.meta0.validate(); err != nil { | |
266 | return err | |
267 | } | |
268 | if err := db.meta1.validate(); err != nil { | |
269 | return err | |
284 | // Validate the meta pages. We only return an error if both meta pages fail | |
285 | // validation, since meta0 failing validation means that it wasn't saved | |
286 | // properly -- but we can recover using meta1. And vice-versa. | |
287 | err0 := db.meta0.validate() | |
288 | err1 := db.meta1.validate() | |
289 | if err0 != nil && err1 != nil { | |
290 | return err0 | |
270 | 291 | } |
271 | 292 | |
272 | 293 | return nil |
338 | 359 | m.root = bucket{root: 3} |
339 | 360 | m.pgid = 4 |
340 | 361 | m.txid = txid(i) |
362 | m.checksum = m.sum64() | |
341 | 363 | } |
342 | 364 | |
343 | 365 | // Write an empty freelist at page 3. |
382 | 404 | if !db.opened { |
383 | 405 | return nil |
384 | 406 | } |
385 | ||
407 | ||
386 | 408 | db.opened = false |
387 | 409 | |
388 | 410 | db.freelist = nil |
389 | db.path = "" | |
390 | 411 | |
391 | 412 | // Clear ops. |
392 | 413 | db.ops.writeAt = nil |
413 | 434 | db.file = nil |
414 | 435 | } |
415 | 436 | |
437 | db.path = "" | |
416 | 438 | return nil |
417 | 439 | } |
418 | 440 | |
777 | 799 | |
778 | 800 | // meta retrieves the current meta page reference. |
779 | 801 | func (db *DB) meta() *meta { |
780 | if db.meta0.txid > db.meta1.txid { | |
781 | return db.meta0 | |
782 | } | |
783 | return db.meta1 | |
802 | // We have to return the meta with the highest txid which doesn't fail | |
803 | // validation. Otherwise, we can cause errors when in fact the database is | |
804 | // in a consistent state. metaA is the one with the higher txid. | |
805 | metaA := db.meta0 | |
806 | metaB := db.meta1 | |
807 | if db.meta1.txid > db.meta0.txid { | |
808 | metaA = db.meta1 | |
809 | metaB = db.meta0 | |
810 | } | |
811 | ||
812 | // Use higher meta page if valid. Otherwise fallback to previous, if valid. | |
813 | if err := metaA.validate(); err == nil { | |
814 | return metaA | |
815 | } else if err := metaB.validate(); err == nil { | |
816 | return metaB | |
817 | } | |
818 | ||
819 | // This should never be reached, because both meta1 and meta0 were validated | |
820 | // on mmap() and we do fsync() on every write. | |
821 | panic("bolt.DB.meta(): invalid meta pages") | |
784 | 822 | } |
785 | 823 | |
786 | 824 | // allocate returns a contiguous block of memory starting at a given page. |
787 | 825 | func (db *DB) allocate(count int) (*page, error) { |
788 | 826 | // Allocate a temporary buffer for the page. |
789 | buf := make([]byte, count*db.pageSize) | |
827 | var buf []byte | |
828 | if count == 1 { | |
829 | buf = db.pagePool.Get().([]byte) | |
830 | } else { | |
831 | buf = make([]byte, count*db.pageSize) | |
832 | } | |
790 | 833 | p := (*page)(unsafe.Pointer(&buf[0])) |
791 | 834 | p.overflow = uint32(count - 1) |
792 | 835 | |
936 | 979 | |
937 | 980 | // validate checks the marker bytes and version of the meta page to ensure it matches this binary. |
938 | 981 | func (m *meta) validate() error { |
939 | if m.checksum != 0 && m.checksum != m.sum64() { | |
940 | return ErrChecksum | |
941 | } else if m.magic != magic { | |
982 | if m.magic != magic { | |
942 | 983 | return ErrInvalid |
943 | 984 | } else if m.version != version { |
944 | 985 | return ErrVersionMismatch |
986 | } else if m.checksum != 0 && m.checksum != m.sum64() { | |
987 | return ErrChecksum | |
945 | 988 | } |
946 | 989 | return nil |
947 | 990 | } |
44 | 44 | _ uint32 |
45 | 45 | _ [16]byte |
46 | 46 | _ uint64 |
47 | _ uint64 | |
47 | pgid uint64 | |
48 | 48 | _ uint64 |
49 | 49 | checksum uint64 |
50 | 50 | } |
84 | 84 | } |
85 | 85 | } |
86 | 86 | |
87 | // Ensure that opening a file with wrong checksum returns ErrChecksum. | |
88 | func TestOpen_ErrChecksum(t *testing.T) { | |
89 | buf := make([]byte, pageSize) | |
90 | meta := (*meta)(unsafe.Pointer(&buf[0])) | |
91 | meta.magic = magic | |
92 | meta.version = version | |
93 | meta.checksum = 123 | |
94 | ||
95 | path := tempfile() | |
96 | f, err := os.Create(path) | |
97 | if err != nil { | |
98 | t.Fatal(err) | |
99 | } | |
100 | if _, err := f.WriteAt(buf, pageHeaderSize); err != nil { | |
101 | t.Fatal(err) | |
102 | } | |
103 | if err := f.Close(); err != nil { | |
104 | t.Fatal(err) | |
105 | } | |
106 | defer os.Remove(path) | |
107 | ||
108 | if _, err := bolt.Open(path, 0666, nil); err != bolt.ErrChecksum { | |
109 | t.Fatalf("unexpected error: %s", err) | |
110 | } | |
111 | } | |
112 | ||
113 | 87 | // Ensure that opening a file that is not a Bolt database returns ErrInvalid. |
114 | 88 | func TestOpen_ErrInvalid(t *testing.T) { |
115 | 89 | path := tempfile() |
131 | 105 | } |
132 | 106 | } |
133 | 107 | |
134 | // Ensure that opening a file created with a different version of Bolt returns | |
135 | // ErrVersionMismatch. | |
108 | // Ensure that opening a file with two invalid versions returns ErrVersionMismatch. | |
136 | 109 | func TestOpen_ErrVersionMismatch(t *testing.T) { |
137 | buf := make([]byte, pageSize) | |
138 | meta := (*meta)(unsafe.Pointer(&buf[0])) | |
139 | meta.magic = magic | |
140 | meta.version = version + 100 | |
141 | ||
142 | path := tempfile() | |
143 | f, err := os.Create(path) | |
144 | if err != nil { | |
145 | t.Fatal(err) | |
146 | } | |
147 | if _, err := f.WriteAt(buf, pageHeaderSize); err != nil { | |
148 | t.Fatal(err) | |
149 | } | |
150 | if err := f.Close(); err != nil { | |
151 | t.Fatal(err) | |
152 | } | |
153 | defer os.Remove(path) | |
154 | ||
110 | if pageSize != os.Getpagesize() { | |
111 | t.Skip("page size mismatch") | |
112 | } | |
113 | ||
114 | // Create empty database. | |
115 | db := MustOpenDB() | |
116 | path := db.Path() | |
117 | defer db.MustClose() | |
118 | ||
119 | // Close database. | |
120 | if err := db.DB.Close(); err != nil { | |
121 | t.Fatal(err) | |
122 | } | |
123 | ||
124 | // Read data file. | |
125 | buf, err := ioutil.ReadFile(path) | |
126 | if err != nil { | |
127 | t.Fatal(err) | |
128 | } | |
129 | ||
130 | // Rewrite meta pages. | |
131 | meta0 := (*meta)(unsafe.Pointer(&buf[pageHeaderSize])) | |
132 | meta0.version++ | |
133 | meta1 := (*meta)(unsafe.Pointer(&buf[pageSize+pageHeaderSize])) | |
134 | meta1.version++ | |
135 | if err := ioutil.WriteFile(path, buf, 0666); err != nil { | |
136 | t.Fatal(err) | |
137 | } | |
138 | ||
139 | // Reopen data file. | |
155 | 140 | if _, err := bolt.Open(path, 0666, nil); err != bolt.ErrVersionMismatch { |
141 | t.Fatalf("unexpected error: %s", err) | |
142 | } | |
143 | } | |
144 | ||
145 | // Ensure that opening a file with two invalid checksums returns ErrChecksum. | |
146 | func TestOpen_ErrChecksum(t *testing.T) { | |
147 | if pageSize != os.Getpagesize() { | |
148 | t.Skip("page size mismatch") | |
149 | } | |
150 | ||
151 | // Create empty database. | |
152 | db := MustOpenDB() | |
153 | path := db.Path() | |
154 | defer db.MustClose() | |
155 | ||
156 | // Close database. | |
157 | if err := db.DB.Close(); err != nil { | |
158 | t.Fatal(err) | |
159 | } | |
160 | ||
161 | // Read data file. | |
162 | buf, err := ioutil.ReadFile(path) | |
163 | if err != nil { | |
164 | t.Fatal(err) | |
165 | } | |
166 | ||
167 | // Rewrite meta pages. | |
168 | meta0 := (*meta)(unsafe.Pointer(&buf[pageHeaderSize])) | |
169 | meta0.pgid++ | |
170 | meta1 := (*meta)(unsafe.Pointer(&buf[pageSize+pageHeaderSize])) | |
171 | meta1.pgid++ | |
172 | if err := ioutil.WriteFile(path, buf, 0666); err != nil { | |
173 | t.Fatal(err) | |
174 | } | |
175 | ||
176 | // Reopen data file. | |
177 | if _, err := bolt.Open(path, 0666, nil); err != bolt.ErrChecksum { | |
156 | 178 | t.Fatalf("unexpected error: %s", err) |
157 | 179 | } |
158 | 180 | } |
11 | 11 | // already open. |
12 | 12 | ErrDatabaseOpen = errors.New("database already open") |
13 | 13 | |
14 | // ErrInvalid is returned when a data file is not a Bolt-formatted database. | |
14 | // ErrInvalid is returned when both meta pages on a database are invalid. | |
15 | // This typically occurs when a file is not a bolt database. | |
15 | 16 | ErrInvalid = errors.New("invalid database") |
16 | 17 | |
17 | 18 | // ErrVersionMismatch is returned when the data file was created with a |
110 | 110 | // key returns a byte slice of the node key. |
111 | 111 | func (n *leafPageElement) key() []byte { |
112 | 112 | buf := (*[maxAllocSize]byte)(unsafe.Pointer(n)) |
113 | return (*[maxAllocSize]byte)(unsafe.Pointer(&buf[n.pos]))[:n.ksize] | |
113 | return (*[maxAllocSize]byte)(unsafe.Pointer(&buf[n.pos]))[:n.ksize:n.ksize] | |
114 | 114 | } |
115 | 115 | |
116 | 116 | // value returns a byte slice of the node value. |
117 | 117 | func (n *leafPageElement) value() []byte { |
118 | 118 | buf := (*[maxAllocSize]byte)(unsafe.Pointer(n)) |
119 | return (*[maxAllocSize]byte)(unsafe.Pointer(&buf[n.pos+n.ksize]))[:n.vsize] | |
119 | return (*[maxAllocSize]byte)(unsafe.Pointer(&buf[n.pos+n.ksize]))[:n.vsize:n.vsize] | |
120 | 120 | } |
121 | 121 | |
122 | 122 | // PageInfo represents human readable information about a page. |
472 | 472 | for _, p := range tx.pages { |
473 | 473 | pages = append(pages, p) |
474 | 474 | } |
475 | // Clear out page cache early. | |
476 | tx.pages = make(map[pgid]*page) | |
475 | 477 | sort.Sort(pages) |
476 | 478 | |
477 | 479 | // Write pages to disk in order. |
516 | 518 | } |
517 | 519 | } |
518 | 520 | |
519 | // Clear out page cache. | |
520 | tx.pages = make(map[pgid]*page) | |
521 | // Put small pages back to page pool. | |
522 | for _, p := range pages { | |
523 | // Ignore page sizes over 1 page. | |
524 | // These are allocated using make() instead of the page pool. | |
525 | if int(p.overflow) != 0 { | |
526 | continue | |
527 | } | |
528 | ||
529 | buf := (*[maxAllocSize]byte)(unsafe.Pointer(p))[:tx.db.pageSize] | |
530 | ||
531 | // See https://go.googlesource.com/go/+/f03c9202c43e0abb130669852082117ca50aa9b1 | |
532 | for i := range buf { | |
533 | buf[i] = 0 | |
534 | } | |
535 | tx.db.pagePool.Put(buf) | |
536 | } | |
521 | 537 | |
522 | 538 | return nil |
523 | 539 | } |