1 package bbolt_test
2
3 import (
4 "bytes"
5 "encoding/binary"
6 "errors"
7 "fmt"
8 "hash/fnv"
9 "log"
10 "math/rand"
11 "os"
12 "path/filepath"
13 "reflect"
14 "sync"
15 "testing"
16 "time"
17 "unsafe"
18
19 "github.com/stretchr/testify/assert"
20 "github.com/stretchr/testify/require"
21
22 bolt "go.etcd.io/bbolt"
23 "go.etcd.io/bbolt/internal/btesting"
24 )
25
26
27 const pageSize = 4096
28
29
30 const pageHeaderSize = 16
31
32
33 type meta struct {
34 _ uint32
35 version uint32
36 _ uint32
37 _ uint32
38 _ [16]byte
39 _ uint64
40 pgid uint64
41 _ uint64
42 _ uint64
43 }
44
45
46 func TestOpen(t *testing.T) {
47 path := tempfile()
48 defer os.RemoveAll(path)
49
50 db, err := bolt.Open(path, 0666, nil)
51 if err != nil {
52 t.Fatal(err)
53 } else if db == nil {
54 t.Fatal("expected db")
55 }
56
57 if s := db.Path(); s != path {
58 t.Fatalf("unexpected path: %s", s)
59 }
60
61 if err := db.Close(); err != nil {
62 t.Fatal(err)
63 }
64 }
65
66
67
68 func TestOpen_MultipleGoroutines(t *testing.T) {
69 if testing.Short() {
70 t.Skip("skipping test in short mode")
71 }
72
73 const (
74 instances = 30
75 iterations = 30
76 )
77 path := tempfile()
78 defer os.RemoveAll(path)
79 var wg sync.WaitGroup
80 errCh := make(chan error, iterations*instances)
81 for iteration := 0; iteration < iterations; iteration++ {
82 for instance := 0; instance < instances; instance++ {
83 wg.Add(1)
84 go func() {
85 defer wg.Done()
86 db, err := bolt.Open(path, 0600, nil)
87 if err != nil {
88 errCh <- err
89 return
90 }
91 if err := db.Close(); err != nil {
92 errCh <- err
93 return
94 }
95 }()
96 }
97 wg.Wait()
98 }
99 close(errCh)
100 for err := range errCh {
101 if err != nil {
102 t.Fatalf("error from inside goroutine: %v", err)
103 }
104 }
105 }
106
107
108 func TestOpen_ErrPathRequired(t *testing.T) {
109 _, err := bolt.Open("", 0666, nil)
110 if err == nil {
111 t.Fatalf("expected error")
112 }
113 }
114
115
116 func TestOpen_ErrNotExists(t *testing.T) {
117 _, err := bolt.Open(filepath.Join(tempfile(), "bad-path"), 0666, nil)
118 if err == nil {
119 t.Fatal("expected error")
120 }
121 }
122
123
124 func TestOpen_ErrInvalid(t *testing.T) {
125 path := tempfile()
126 defer os.RemoveAll(path)
127
128 f, err := os.Create(path)
129 if err != nil {
130 t.Fatal(err)
131 }
132 if _, err := fmt.Fprintln(f, "this is not a bolt database"); err != nil {
133 t.Fatal(err)
134 }
135 if err := f.Close(); err != nil {
136 t.Fatal(err)
137 }
138
139 if _, err := bolt.Open(path, 0666, nil); err != bolt.ErrInvalid {
140 t.Fatalf("unexpected error: %s", err)
141 }
142 }
143
144
145 func TestOpen_ErrVersionMismatch(t *testing.T) {
146 if pageSize != os.Getpagesize() {
147 t.Skip("page size mismatch")
148 }
149
150
151 db := btesting.MustCreateDB(t)
152 path := db.Path()
153
154
155 if err := db.Close(); err != nil {
156 t.Fatal(err)
157 }
158
159
160 buf, err := os.ReadFile(path)
161 if err != nil {
162 t.Fatal(err)
163 }
164
165
166 meta0 := (*meta)(unsafe.Pointer(&buf[pageHeaderSize]))
167 meta0.version++
168 meta1 := (*meta)(unsafe.Pointer(&buf[pageSize+pageHeaderSize]))
169 meta1.version++
170 if err := os.WriteFile(path, buf, 0666); err != nil {
171 t.Fatal(err)
172 }
173
174
175 if _, err := bolt.Open(path, 0666, nil); err != bolt.ErrVersionMismatch {
176 t.Fatalf("unexpected error: %s", err)
177 }
178 }
179
180
181 func TestOpen_ErrChecksum(t *testing.T) {
182 if pageSize != os.Getpagesize() {
183 t.Skip("page size mismatch")
184 }
185
186
187 db := btesting.MustCreateDB(t)
188 path := db.Path()
189
190
191 if err := db.Close(); err != nil {
192 t.Fatal(err)
193 }
194
195
196 buf, err := os.ReadFile(path)
197 if err != nil {
198 t.Fatal(err)
199 }
200
201
202 meta0 := (*meta)(unsafe.Pointer(&buf[pageHeaderSize]))
203 meta0.pgid++
204 meta1 := (*meta)(unsafe.Pointer(&buf[pageSize+pageHeaderSize]))
205 meta1.pgid++
206 if err := os.WriteFile(path, buf, 0666); err != nil {
207 t.Fatal(err)
208 }
209
210
211 if _, err := bolt.Open(path, 0666, nil); err != bolt.ErrChecksum {
212 t.Fatalf("unexpected error: %s", err)
213 }
214 }
215
216
217
218 func TestOpen_ReadPageSize_FromMeta1_OS(t *testing.T) {
219
220 db := btesting.MustCreateDB(t)
221 path := db.Path()
222
223 db.MustClose()
224
225
226 buf, err := os.ReadFile(path)
227 if err != nil {
228 t.Fatal(err)
229 }
230
231
232 meta0 := (*meta)(unsafe.Pointer(&buf[pageHeaderSize]))
233 meta0.pgid++
234 if err := os.WriteFile(path, buf, 0666); err != nil {
235 t.Fatal(err)
236 }
237
238
239 db = btesting.MustOpenDBWithOption(t, path, nil)
240 require.Equalf(t, os.Getpagesize(), db.Info().PageSize, "check page size failed")
241 }
242
243
244
245 func TestOpen_ReadPageSize_FromMeta1_Given(t *testing.T) {
246
247 for i := 0; i <= 14; i++ {
248 givenPageSize := 1024 << uint(i)
249 t.Logf("Testing page size %d", givenPageSize)
250
251 db := btesting.MustCreateDBWithOption(t, &bolt.Options{PageSize: givenPageSize})
252 path := db.Path()
253
254 db.MustClose()
255
256
257 buf, err := os.ReadFile(path)
258 require.NoError(t, err)
259
260
261 if i%3 == 0 {
262 t.Logf("#%d: Intentionally corrupt the first meta page for pageSize %d", i, givenPageSize)
263 meta0 := (*meta)(unsafe.Pointer(&buf[pageHeaderSize]))
264 meta0.pgid++
265 err = os.WriteFile(path, buf, 0666)
266 require.NoError(t, err)
267 }
268
269
270 db = btesting.MustOpenDBWithOption(t, path, nil)
271 require.Equalf(t, givenPageSize, db.Info().PageSize, "check page size failed")
272 db.MustClose()
273 }
274 }
275
276
277
278 func TestOpen_Size(t *testing.T) {
279
280 db := btesting.MustCreateDB(t)
281
282 pagesize := db.Info().PageSize
283
284
285 err := db.Fill([]byte("data"), 1, 10000,
286 func(tx int, k int) []byte { return []byte(fmt.Sprintf("%04d", k)) },
287 func(tx int, k int) []byte { return make([]byte, 1000) },
288 )
289 if err != nil {
290 t.Fatal(err)
291 }
292
293 path := db.Path()
294 db.MustClose()
295
296 sz := fileSize(path)
297 if sz == 0 {
298 t.Fatalf("unexpected new file size: %d", sz)
299 }
300
301 db.MustReopen()
302 if err := db.Update(func(tx *bolt.Tx) error {
303 if err := tx.Bucket([]byte("data")).Put([]byte{0}, []byte{0}); err != nil {
304 t.Fatal(err)
305 }
306 return nil
307 }); err != nil {
308 t.Fatal(err)
309 }
310 if err := db.Close(); err != nil {
311 t.Fatal(err)
312 }
313 newSz := fileSize(path)
314 if newSz == 0 {
315 t.Fatalf("unexpected new file size: %d", newSz)
316 }
317
318
319
320 if sz < newSz-5*int64(pagesize) {
321 t.Fatalf("unexpected file growth: %d => %d", sz, newSz)
322 }
323 }
324
325
326
327 func TestOpen_Size_Large(t *testing.T) {
328 if testing.Short() {
329 t.Skip("short mode")
330 }
331
332
333 db := btesting.MustCreateDB(t)
334 path := db.Path()
335
336 pagesize := db.Info().PageSize
337
338
339 var index uint64
340 for i := 0; i < 10000; i++ {
341 if err := db.Update(func(tx *bolt.Tx) error {
342 b, _ := tx.CreateBucketIfNotExists([]byte("data"))
343 for j := 0; j < 1000; j++ {
344 if err := b.Put(u64tob(index), make([]byte, 50)); err != nil {
345 t.Fatal(err)
346 }
347 index++
348 }
349 return nil
350 }); err != nil {
351 t.Fatal(err)
352 }
353 }
354
355
356 if err := db.Close(); err != nil {
357 t.Fatal(err)
358 }
359 sz := fileSize(path)
360 if sz == 0 {
361 t.Fatalf("unexpected new file size: %d", sz)
362 } else if sz < (1 << 30) {
363 t.Fatalf("expected larger initial size: %d", sz)
364 }
365
366
367 db0, err := bolt.Open(path, 0666, nil)
368 if err != nil {
369 t.Fatal(err)
370 }
371 if err := db0.Update(func(tx *bolt.Tx) error {
372 return tx.Bucket([]byte("data")).Put([]byte{0}, []byte{0})
373 }); err != nil {
374 t.Fatal(err)
375 }
376 if err := db0.Close(); err != nil {
377 t.Fatal(err)
378 }
379
380 newSz := fileSize(path)
381 if newSz == 0 {
382 t.Fatalf("unexpected new file size: %d", newSz)
383 }
384
385
386
387 if sz < newSz-5*int64(pagesize) {
388 t.Fatalf("unexpected file growth: %d => %d", sz, newSz)
389 }
390 }
391
392
393 func TestOpen_Check(t *testing.T) {
394 path := tempfile()
395 defer os.RemoveAll(path)
396
397 db, err := bolt.Open(path, 0666, nil)
398 if err != nil {
399 t.Fatal(err)
400 }
401 if err = db.View(func(tx *bolt.Tx) error { return <-tx.Check() }); err != nil {
402 t.Fatal(err)
403 }
404 if err = db.Close(); err != nil {
405 t.Fatal(err)
406 }
407
408 db, err = bolt.Open(path, 0666, nil)
409 if err != nil {
410 t.Fatal(err)
411 }
412 if err := db.View(func(tx *bolt.Tx) error { return <-tx.Check() }); err != nil {
413 t.Fatal(err)
414 }
415 if err := db.Close(); err != nil {
416 t.Fatal(err)
417 }
418 }
419
420
421 func TestOpen_MetaInitWriteError(t *testing.T) {
422 t.Skip("pending")
423 }
424
425
426 func TestOpen_FileTooSmall(t *testing.T) {
427 path := tempfile()
428 defer os.RemoveAll(path)
429
430 db, err := bolt.Open(path, 0666, nil)
431 if err != nil {
432 t.Fatal(err)
433 }
434 pageSize := int64(db.Info().PageSize)
435 if err = db.Close(); err != nil {
436 t.Fatal(err)
437 }
438
439
440 if err = os.Truncate(path, pageSize); err != nil {
441 t.Fatal(err)
442 }
443
444 _, err = bolt.Open(path, 0666, nil)
445 if err == nil || err.Error() != "file size too small" {
446 t.Fatalf("unexpected error: %s", err)
447 }
448 }
449
450
451
452
453
454 func TestDB_Open_InitialMmapSize(t *testing.T) {
455 path := tempfile()
456 defer os.Remove(path)
457
458 initMmapSize := 1 << 30
459 testWriteSize := 1 << 27
460
461 db, err := bolt.Open(path, 0666, &bolt.Options{InitialMmapSize: initMmapSize})
462 if err != nil {
463 t.Fatal(err)
464 }
465
466
467
468 rtx, err := db.Begin(false)
469 if err != nil {
470 t.Fatal(err)
471 }
472
473
474 wtx, err := db.Begin(true)
475 if err != nil {
476 t.Fatal(err)
477 }
478
479 b, err := wtx.CreateBucket([]byte("test"))
480 if err != nil {
481 t.Fatal(err)
482 }
483
484
485 err = b.Put([]byte("foo"), make([]byte, testWriteSize))
486 if err != nil {
487 t.Fatal(err)
488 }
489
490 done := make(chan error, 1)
491
492 go func() {
493 err := wtx.Commit()
494 done <- err
495 }()
496
497 select {
498 case <-time.After(5 * time.Second):
499 t.Errorf("unexpected that the reader blocks writer")
500 case err := <-done:
501 if err != nil {
502 t.Fatal(err)
503 }
504 }
505
506 if err := rtx.Rollback(); err != nil {
507 t.Fatal(err)
508 }
509 }
510
511
512 func TestDB_Open_ReadOnly(t *testing.T) {
513
514 db := btesting.MustCreateDB(t)
515
516 if err := db.Update(func(tx *bolt.Tx) error {
517 b, err := tx.CreateBucket([]byte("widgets"))
518 if err != nil {
519 t.Fatal(err)
520 }
521 if err := b.Put([]byte("foo"), []byte("bar")); err != nil {
522 t.Fatal(err)
523 }
524 return nil
525 }); err != nil {
526 t.Fatal(err)
527 }
528 if err := db.Close(); err != nil {
529 t.Fatal(err)
530 }
531
532 f := db.Path()
533 o := &bolt.Options{ReadOnly: true}
534 readOnlyDB, err := bolt.Open(f, 0666, o)
535 if err != nil {
536 panic(err)
537 }
538
539 if !readOnlyDB.IsReadOnly() {
540 t.Fatal("expect db in read only mode")
541 }
542
543
544 if err := readOnlyDB.View(func(tx *bolt.Tx) error {
545 value := tx.Bucket([]byte("widgets")).Get([]byte("foo"))
546 if !bytes.Equal(value, []byte("bar")) {
547 t.Fatal("expect value 'bar', got", value)
548 }
549 return nil
550 }); err != nil {
551 t.Fatal(err)
552 }
553
554
555 if _, err := readOnlyDB.Begin(true); err != bolt.ErrDatabaseReadOnly {
556 t.Fatalf("unexpected error: %s", err)
557 }
558
559 if err := readOnlyDB.Close(); err != nil {
560 t.Fatal(err)
561 }
562 }
563
564
565
566 func TestOpen_BigPage(t *testing.T) {
567 pageSize := os.Getpagesize()
568
569 db1 := btesting.MustCreateDBWithOption(t, &bolt.Options{PageSize: pageSize * 2})
570
571 db2 := btesting.MustCreateDBWithOption(t, &bolt.Options{PageSize: pageSize * 4})
572
573 if db1sz, db2sz := fileSize(db1.Path()), fileSize(db2.Path()); db1sz >= db2sz {
574 t.Errorf("expected %d < %d", db1sz, db2sz)
575 }
576 }
577
578
579
580
581 func TestOpen_RecoverFreeList(t *testing.T) {
582 db := btesting.MustCreateDBWithOption(t, &bolt.Options{NoFreelistSync: true})
583
584
585 tx, err := db.Begin(true)
586 if err != nil {
587 t.Fatal(err)
588 }
589 wbuf := make([]byte, 8192)
590 for i := 0; i < 100; i++ {
591 s := fmt.Sprintf("%d", i)
592 b, err := tx.CreateBucket([]byte(s))
593 if err != nil {
594 t.Fatal(err)
595 }
596 if err = b.Put([]byte(s), wbuf); err != nil {
597 t.Fatal(err)
598 }
599 }
600 if err = tx.Commit(); err != nil {
601 t.Fatal(err)
602 }
603
604
605 if tx, err = db.Begin(true); err != nil {
606 t.Fatal(err)
607 }
608 for i := 0; i < 50; i++ {
609 s := fmt.Sprintf("%d", i)
610 b := tx.Bucket([]byte(s))
611 if b == nil {
612 t.Fatal(err)
613 }
614 if err := b.Delete([]byte(s)); err != nil {
615 t.Fatal(err)
616 }
617 }
618 if err := tx.Commit(); err != nil {
619 t.Fatal(err)
620 }
621 db.MustClose()
622
623
624 db.MustReopen()
625 freepages := db.Stats().FreePageN
626 if freepages == 0 {
627 t.Fatalf("no free pages on NoFreelistSync reopen")
628 }
629 db.MustClose()
630
631
632 db.SetOptions(&bolt.Options{})
633 db.MustReopen()
634
635 freepages--
636 if fp := db.Stats().FreePageN; fp < freepages {
637 t.Fatalf("closed with %d free pages, opened with %d", freepages, fp)
638 }
639 }
640
641
642 func TestDB_Begin_ErrDatabaseNotOpen(t *testing.T) {
643 var db bolt.DB
644 if _, err := db.Begin(false); err != bolt.ErrDatabaseNotOpen {
645 t.Fatalf("unexpected error: %s", err)
646 }
647 }
648
649
650 func TestDB_BeginRW(t *testing.T) {
651 db := btesting.MustCreateDB(t)
652
653 tx, err := db.Begin(true)
654 require.NoError(t, err)
655 require.NotNil(t, tx, "expected tx")
656 defer func() { require.NoError(t, tx.Commit()) }()
657
658 require.True(t, tx.Writable(), "expected writable tx")
659 require.Same(t, db.DB, tx.DB())
660 }
661
662
663
664 func TestDB_Concurrent_WriteTo(t *testing.T) {
665 o := &bolt.Options{NoFreelistSync: false}
666 db := btesting.MustCreateDBWithOption(t, o)
667
668 var wg sync.WaitGroup
669 wtxs, rtxs := 5, 5
670 wg.Add(wtxs * rtxs)
671 f := func(tx *bolt.Tx) {
672 defer wg.Done()
673 f, err := os.CreateTemp("", "bolt-")
674 if err != nil {
675 panic(err)
676 }
677 time.Sleep(time.Duration(rand.Intn(20)+1) * time.Millisecond)
678 _, err = tx.WriteTo(f)
679 if err != nil {
680 panic(err)
681 }
682 err = tx.Rollback()
683 if err != nil {
684 panic(err)
685 }
686 f.Close()
687
688 copyOpt := *o
689 snap := btesting.MustOpenDBWithOption(t, f.Name(), ©Opt)
690 defer snap.MustClose()
691 snap.MustCheck()
692 }
693
694 tx1, err := db.Begin(true)
695 if err != nil {
696 t.Fatal(err)
697 }
698 if _, err := tx1.CreateBucket([]byte("abc")); err != nil {
699 t.Fatal(err)
700 }
701 if err := tx1.Commit(); err != nil {
702 t.Fatal(err)
703 }
704
705 for i := 0; i < wtxs; i++ {
706 tx, err := db.Begin(true)
707 if err != nil {
708 t.Fatal(err)
709 }
710 if err := tx.Bucket([]byte("abc")).Put([]byte{0}, []byte{0}); err != nil {
711 t.Fatal(err)
712 }
713 for j := 0; j < rtxs; j++ {
714 rtx, rerr := db.Begin(false)
715 if rerr != nil {
716 t.Fatal(rerr)
717 }
718 go f(rtx)
719 }
720 if err := tx.Commit(); err != nil {
721 t.Fatal(err)
722 }
723 }
724 wg.Wait()
725 }
726
727
728 func TestDB_BeginRW_Closed(t *testing.T) {
729 var db bolt.DB
730 if _, err := db.Begin(true); err != bolt.ErrDatabaseNotOpen {
731 t.Fatalf("unexpected error: %s", err)
732 }
733 }
734
735 func TestDB_Close_PendingTx_RW(t *testing.T) { testDB_Close_PendingTx(t, true) }
736 func TestDB_Close_PendingTx_RO(t *testing.T) { testDB_Close_PendingTx(t, false) }
737
738
739 func testDB_Close_PendingTx(t *testing.T, writable bool) {
740 db := btesting.MustCreateDB(t)
741
742
743 tx, err := db.Begin(writable)
744 if err != nil {
745 t.Fatal(err)
746 }
747
748
749 done := make(chan error, 1)
750 go func() {
751 err := db.Close()
752 done <- err
753 }()
754
755
756 time.Sleep(100 * time.Millisecond)
757 select {
758 case err := <-done:
759 if err != nil {
760 t.Errorf("error from inside goroutine: %v", err)
761 }
762 t.Fatal("database closed too early")
763 default:
764 }
765
766
767 if writable {
768 err = tx.Commit()
769 } else {
770 err = tx.Rollback()
771 }
772 if err != nil {
773 t.Fatal(err)
774 }
775
776
777 time.Sleep(100 * time.Millisecond)
778 select {
779 case err := <-done:
780 if err != nil {
781 t.Fatalf("error from inside goroutine: %v", err)
782 }
783 default:
784 t.Fatal("database did not close")
785 }
786 }
787
788
789 func TestDB_Update(t *testing.T) {
790 db := btesting.MustCreateDB(t)
791 if err := db.Update(func(tx *bolt.Tx) error {
792 b, err := tx.CreateBucket([]byte("widgets"))
793 if err != nil {
794 t.Fatal(err)
795 }
796 if err := b.Put([]byte("foo"), []byte("bar")); err != nil {
797 t.Fatal(err)
798 }
799 if err := b.Put([]byte("baz"), []byte("bat")); err != nil {
800 t.Fatal(err)
801 }
802 if err := b.Delete([]byte("foo")); err != nil {
803 t.Fatal(err)
804 }
805 return nil
806 }); err != nil {
807 t.Fatal(err)
808 }
809 if err := db.View(func(tx *bolt.Tx) error {
810 b := tx.Bucket([]byte("widgets"))
811 if v := b.Get([]byte("foo")); v != nil {
812 t.Fatalf("expected nil value, got: %v", v)
813 }
814 if v := b.Get([]byte("baz")); !bytes.Equal(v, []byte("bat")) {
815 t.Fatalf("unexpected value: %v", v)
816 }
817 return nil
818 }); err != nil {
819 t.Fatal(err)
820 }
821 }
822
823
824 func TestDB_Update_Closed(t *testing.T) {
825 var db bolt.DB
826 if err := db.Update(func(tx *bolt.Tx) error {
827 if _, err := tx.CreateBucket([]byte("widgets")); err != nil {
828 t.Fatal(err)
829 }
830 return nil
831 }); err != bolt.ErrDatabaseNotOpen {
832 t.Fatalf("unexpected error: %s", err)
833 }
834 }
835
836
837 func TestDB_Update_ManualCommit(t *testing.T) {
838 db := btesting.MustCreateDB(t)
839
840 var panicked bool
841 if err := db.Update(func(tx *bolt.Tx) error {
842 func() {
843 defer func() {
844 if r := recover(); r != nil {
845 panicked = true
846 }
847 }()
848
849 if err := tx.Commit(); err != nil {
850 t.Fatal(err)
851 }
852 }()
853 return nil
854 }); err != nil {
855 t.Fatal(err)
856 } else if !panicked {
857 t.Fatal("expected panic")
858 }
859 }
860
861
862 func TestDB_Update_ManualRollback(t *testing.T) {
863 db := btesting.MustCreateDB(t)
864
865 var panicked bool
866 if err := db.Update(func(tx *bolt.Tx) error {
867 func() {
868 defer func() {
869 if r := recover(); r != nil {
870 panicked = true
871 }
872 }()
873
874 if err := tx.Rollback(); err != nil {
875 t.Fatal(err)
876 }
877 }()
878 return nil
879 }); err != nil {
880 t.Fatal(err)
881 } else if !panicked {
882 t.Fatal("expected panic")
883 }
884 }
885
886
887 func TestDB_View_ManualCommit(t *testing.T) {
888 db := btesting.MustCreateDB(t)
889
890 var panicked bool
891 if err := db.View(func(tx *bolt.Tx) error {
892 func() {
893 defer func() {
894 if r := recover(); r != nil {
895 panicked = true
896 }
897 }()
898
899 if err := tx.Commit(); err != nil {
900 t.Fatal(err)
901 }
902 }()
903 return nil
904 }); err != nil {
905 t.Fatal(err)
906 } else if !panicked {
907 t.Fatal("expected panic")
908 }
909 }
910
911
912 func TestDB_View_ManualRollback(t *testing.T) {
913 db := btesting.MustCreateDB(t)
914
915 var panicked bool
916 if err := db.View(func(tx *bolt.Tx) error {
917 func() {
918 defer func() {
919 if r := recover(); r != nil {
920 panicked = true
921 }
922 }()
923
924 if err := tx.Rollback(); err != nil {
925 t.Fatal(err)
926 }
927 }()
928 return nil
929 }); err != nil {
930 t.Fatal(err)
931 } else if !panicked {
932 t.Fatal("expected panic")
933 }
934 }
935
936
937 func TestDB_Update_Panic(t *testing.T) {
938 db := btesting.MustCreateDB(t)
939
940
941 func() {
942 defer func() {
943 if r := recover(); r != nil {
944 t.Log("recover: update", r)
945 }
946 }()
947
948 if err := db.Update(func(tx *bolt.Tx) error {
949 if _, err := tx.CreateBucket([]byte("widgets")); err != nil {
950 t.Fatal(err)
951 }
952 panic("omg")
953 }); err != nil {
954 t.Fatal(err)
955 }
956 }()
957
958
959 if err := db.Update(func(tx *bolt.Tx) error {
960 if _, err := tx.CreateBucket([]byte("widgets")); err != nil {
961 t.Fatal(err)
962 }
963 return nil
964 }); err != nil {
965 t.Fatal(err)
966 }
967
968
969 if err := db.Update(func(tx *bolt.Tx) error {
970 if tx.Bucket([]byte("widgets")) == nil {
971 t.Fatal("expected bucket")
972 }
973 return nil
974 }); err != nil {
975 t.Fatal(err)
976 }
977 }
978
979
980 func TestDB_View_Error(t *testing.T) {
981 db := btesting.MustCreateDB(t)
982
983 if err := db.View(func(tx *bolt.Tx) error {
984 return errors.New("xxx")
985 }); err == nil || err.Error() != "xxx" {
986 t.Fatalf("unexpected error: %s", err)
987 }
988 }
989
990
991 func TestDB_View_Panic(t *testing.T) {
992 db := btesting.MustCreateDB(t)
993
994 if err := db.Update(func(tx *bolt.Tx) error {
995 if _, err := tx.CreateBucket([]byte("widgets")); err != nil {
996 t.Fatal(err)
997 }
998 return nil
999 }); err != nil {
1000 t.Fatal(err)
1001 }
1002
1003
1004 func() {
1005 defer func() {
1006 if r := recover(); r != nil {
1007 t.Log("recover: view", r)
1008 }
1009 }()
1010
1011 if err := db.View(func(tx *bolt.Tx) error {
1012 if tx.Bucket([]byte("widgets")) == nil {
1013 t.Fatal("expected bucket")
1014 }
1015 panic("omg")
1016 }); err != nil {
1017 t.Fatal(err)
1018 }
1019 }()
1020
1021
1022 if err := db.View(func(tx *bolt.Tx) error {
1023 if tx.Bucket([]byte("widgets")) == nil {
1024 t.Fatal("expected bucket")
1025 }
1026 return nil
1027 }); err != nil {
1028 t.Fatal(err)
1029 }
1030 }
1031
1032
1033 func TestDB_Stats(t *testing.T) {
1034 db := btesting.MustCreateDB(t)
1035 if err := db.Update(func(tx *bolt.Tx) error {
1036 _, err := tx.CreateBucket([]byte("widgets"))
1037 return err
1038 }); err != nil {
1039 t.Fatal(err)
1040 }
1041
1042 stats := db.Stats()
1043 if stats.TxStats.GetPageCount() != 2 {
1044 t.Fatalf("unexpected TxStats.PageCount: %d", stats.TxStats.GetPageCount())
1045 } else if stats.FreePageN != 0 {
1046 t.Fatalf("unexpected FreePageN != 0: %d", stats.FreePageN)
1047 } else if stats.PendingPageN != 2 {
1048 t.Fatalf("unexpected PendingPageN != 2: %d", stats.PendingPageN)
1049 }
1050 }
1051
1052
1053 func TestDB_Consistency(t *testing.T) {
1054 db := btesting.MustCreateDB(t)
1055 if err := db.Update(func(tx *bolt.Tx) error {
1056 _, err := tx.CreateBucket([]byte("widgets"))
1057 return err
1058 }); err != nil {
1059 t.Fatal(err)
1060 }
1061
1062 for i := 0; i < 10; i++ {
1063 if err := db.Update(func(tx *bolt.Tx) error {
1064 if err := tx.Bucket([]byte("widgets")).Put([]byte("foo"), []byte("bar")); err != nil {
1065 t.Fatal(err)
1066 }
1067 return nil
1068 }); err != nil {
1069 t.Fatal(err)
1070 }
1071 }
1072
1073 if err := db.Update(func(tx *bolt.Tx) error {
1074 if p, _ := tx.Page(0); p == nil {
1075 t.Fatal("expected page")
1076 } else if p.Type != "meta" {
1077 t.Fatalf("unexpected page type: %s", p.Type)
1078 }
1079
1080 if p, _ := tx.Page(1); p == nil {
1081 t.Fatal("expected page")
1082 } else if p.Type != "meta" {
1083 t.Fatalf("unexpected page type: %s", p.Type)
1084 }
1085
1086 if p, _ := tx.Page(2); p == nil {
1087 t.Fatal("expected page")
1088 } else if p.Type != "free" {
1089 t.Fatalf("unexpected page type: %s", p.Type)
1090 }
1091
1092 if p, _ := tx.Page(3); p == nil {
1093 t.Fatal("expected page")
1094 } else if p.Type != "free" {
1095 t.Fatalf("unexpected page type: %s", p.Type)
1096 }
1097
1098 if p, _ := tx.Page(4); p == nil {
1099 t.Fatal("expected page")
1100 } else if p.Type != "leaf" {
1101 t.Fatalf("unexpected page type: %s", p.Type)
1102 }
1103
1104 if p, _ := tx.Page(5); p == nil {
1105 t.Fatal("expected page")
1106 } else if p.Type != "freelist" {
1107 t.Fatalf("unexpected page type: %s", p.Type)
1108 }
1109
1110 if p, _ := tx.Page(6); p != nil {
1111 t.Fatal("unexpected page")
1112 }
1113 return nil
1114 }); err != nil {
1115 t.Fatal(err)
1116 }
1117 }
1118
1119
1120 func TestDBStats_Sub(t *testing.T) {
1121 var a, b bolt.Stats
1122 a.TxStats.PageCount = 3
1123 a.FreePageN = 4
1124 b.TxStats.PageCount = 10
1125 b.FreePageN = 14
1126 diff := b.Sub(&a)
1127 if diff.TxStats.GetPageCount() != 7 {
1128 t.Fatalf("unexpected TxStats.PageCount: %d", diff.TxStats.GetPageCount())
1129 }
1130
1131
1132 if diff.FreePageN != 14 {
1133 t.Fatalf("unexpected FreePageN: %d", diff.FreePageN)
1134 }
1135 }
1136
1137
1138 func TestDB_Batch(t *testing.T) {
1139 db := btesting.MustCreateDB(t)
1140
1141 if err := db.Update(func(tx *bolt.Tx) error {
1142 if _, err := tx.CreateBucket([]byte("widgets")); err != nil {
1143 t.Fatal(err)
1144 }
1145 return nil
1146 }); err != nil {
1147 t.Fatal(err)
1148 }
1149
1150
1151 n := 2
1152 ch := make(chan error, n)
1153 for i := 0; i < n; i++ {
1154 go func(i int) {
1155 ch <- db.Batch(func(tx *bolt.Tx) error {
1156 return tx.Bucket([]byte("widgets")).Put(u64tob(uint64(i)), []byte{})
1157 })
1158 }(i)
1159 }
1160
1161
1162 for i := 0; i < n; i++ {
1163 if err := <-ch; err != nil {
1164 t.Fatal(err)
1165 }
1166 }
1167
1168
1169 if err := db.View(func(tx *bolt.Tx) error {
1170 b := tx.Bucket([]byte("widgets"))
1171 for i := 0; i < n; i++ {
1172 if v := b.Get(u64tob(uint64(i))); v == nil {
1173 t.Errorf("key not found: %d", i)
1174 }
1175 }
1176 return nil
1177 }); err != nil {
1178 t.Fatal(err)
1179 }
1180 }
1181
1182 func TestDB_Batch_Panic(t *testing.T) {
1183 db := btesting.MustCreateDB(t)
1184
1185 var sentinel int
1186 var bork = &sentinel
1187 var problem interface{}
1188 var err error
1189
1190
1191 func() {
1192 defer func() {
1193 if p := recover(); p != nil {
1194 problem = p
1195 }
1196 }()
1197 err = db.Batch(func(tx *bolt.Tx) error {
1198 panic(bork)
1199 })
1200 }()
1201
1202
1203 if g, e := err, error(nil); g != e {
1204 t.Fatalf("wrong error: %v != %v", g, e)
1205 }
1206
1207 if g, e := problem, bork; g != e {
1208 t.Fatalf("wrong error: %v != %v", g, e)
1209 }
1210 }
1211
1212 func TestDB_BatchFull(t *testing.T) {
1213 db := btesting.MustCreateDB(t)
1214 if err := db.Update(func(tx *bolt.Tx) error {
1215 _, err := tx.CreateBucket([]byte("widgets"))
1216 return err
1217 }); err != nil {
1218 t.Fatal(err)
1219 }
1220
1221 const size = 3
1222
1223 ch := make(chan error, size)
1224 put := func(i int) {
1225 ch <- db.Batch(func(tx *bolt.Tx) error {
1226 return tx.Bucket([]byte("widgets")).Put(u64tob(uint64(i)), []byte{})
1227 })
1228 }
1229
1230 db.MaxBatchSize = size
1231
1232 db.MaxBatchDelay = 1 * time.Hour
1233
1234 go put(1)
1235 go put(2)
1236
1237
1238 time.Sleep(10 * time.Millisecond)
1239
1240
1241 select {
1242 case <-ch:
1243 t.Fatalf("batch triggered too early")
1244 default:
1245 }
1246
1247 go put(3)
1248
1249
1250 for i := 0; i < size; i++ {
1251 if err := <-ch; err != nil {
1252 t.Fatal(err)
1253 }
1254 }
1255
1256
1257 if err := db.View(func(tx *bolt.Tx) error {
1258 b := tx.Bucket([]byte("widgets"))
1259 for i := 1; i <= size; i++ {
1260 if v := b.Get(u64tob(uint64(i))); v == nil {
1261 t.Errorf("key not found: %d", i)
1262 }
1263 }
1264 return nil
1265 }); err != nil {
1266 t.Fatal(err)
1267 }
1268 }
1269
1270 func TestDB_BatchTime(t *testing.T) {
1271 db := btesting.MustCreateDB(t)
1272 if err := db.Update(func(tx *bolt.Tx) error {
1273 _, err := tx.CreateBucket([]byte("widgets"))
1274 return err
1275 }); err != nil {
1276 t.Fatal(err)
1277 }
1278
1279 const size = 1
1280
1281 ch := make(chan error, size)
1282 put := func(i int) {
1283 ch <- db.Batch(func(tx *bolt.Tx) error {
1284 return tx.Bucket([]byte("widgets")).Put(u64tob(uint64(i)), []byte{})
1285 })
1286 }
1287
1288 db.MaxBatchSize = 1000
1289 db.MaxBatchDelay = 0
1290
1291 go put(1)
1292
1293
1294
1295
1296 for i := 0; i < size; i++ {
1297 if err := <-ch; err != nil {
1298 t.Fatal(err)
1299 }
1300 }
1301
1302
1303 if err := db.View(func(tx *bolt.Tx) error {
1304 b := tx.Bucket([]byte("widgets"))
1305 for i := 1; i <= size; i++ {
1306 if v := b.Get(u64tob(uint64(i))); v == nil {
1307 t.Errorf("key not found: %d", i)
1308 }
1309 }
1310 return nil
1311 }); err != nil {
1312 t.Fatal(err)
1313 }
1314 }
1315
1316
1317
1318 func TestDBUnmap(t *testing.T) {
1319 db := btesting.MustCreateDB(t)
1320
1321 require.NoError(t, db.DB.Close())
1322
1323
1324
1325
1326 v := reflect.ValueOf(*db.DB)
1327 dataref := v.FieldByName("dataref")
1328 data := v.FieldByName("data")
1329 datasz := v.FieldByName("datasz")
1330 assert.True(t, dataref.IsNil())
1331 assert.True(t, data.IsNil())
1332 assert.True(t, datasz.IsZero())
1333
1334
1335 db.DB = nil
1336 }
1337
1338 func ExampleDB_Update() {
1339
1340 db, err := bolt.Open(tempfile(), 0666, nil)
1341 if err != nil {
1342 log.Fatal(err)
1343 }
1344 defer os.Remove(db.Path())
1345
1346
1347 if err := db.Update(func(tx *bolt.Tx) error {
1348 b, err := tx.CreateBucket([]byte("widgets"))
1349 if err != nil {
1350 return err
1351 }
1352 if err := b.Put([]byte("foo"), []byte("bar")); err != nil {
1353 return err
1354 }
1355 return nil
1356 }); err != nil {
1357 log.Fatal(err)
1358 }
1359
1360
1361 if err := db.View(func(tx *bolt.Tx) error {
1362 value := tx.Bucket([]byte("widgets")).Get([]byte("foo"))
1363 fmt.Printf("The value of 'foo' is: %s\n", value)
1364 return nil
1365 }); err != nil {
1366 log.Fatal(err)
1367 }
1368
1369
1370 if err := db.Close(); err != nil {
1371 log.Fatal(err)
1372 }
1373
1374
1375
1376 }
1377
1378 func ExampleDB_View() {
1379
1380 db, err := bolt.Open(tempfile(), 0666, nil)
1381 if err != nil {
1382 log.Fatal(err)
1383 }
1384 defer os.Remove(db.Path())
1385
1386
1387 if err := db.Update(func(tx *bolt.Tx) error {
1388 b, err := tx.CreateBucket([]byte("people"))
1389 if err != nil {
1390 return err
1391 }
1392 if err := b.Put([]byte("john"), []byte("doe")); err != nil {
1393 return err
1394 }
1395 if err := b.Put([]byte("susy"), []byte("que")); err != nil {
1396 return err
1397 }
1398 return nil
1399 }); err != nil {
1400 log.Fatal(err)
1401 }
1402
1403
1404 if err := db.View(func(tx *bolt.Tx) error {
1405 v := tx.Bucket([]byte("people")).Get([]byte("john"))
1406 fmt.Printf("John's last name is %s.\n", v)
1407 return nil
1408 }); err != nil {
1409 log.Fatal(err)
1410 }
1411
1412
1413 if err := db.Close(); err != nil {
1414 log.Fatal(err)
1415 }
1416
1417
1418
1419 }
1420
1421 func ExampleDB_Begin() {
1422
1423 db, err := bolt.Open(tempfile(), 0666, nil)
1424 if err != nil {
1425 log.Fatal(err)
1426 }
1427 defer os.Remove(db.Path())
1428
1429
1430 if err = db.Update(func(tx *bolt.Tx) error {
1431 _, err := tx.CreateBucket([]byte("widgets"))
1432 return err
1433 }); err != nil {
1434 log.Fatal(err)
1435 }
1436
1437
1438 tx, err := db.Begin(true)
1439 if err != nil {
1440 log.Fatal(err)
1441 }
1442 b := tx.Bucket([]byte("widgets"))
1443 if err = b.Put([]byte("john"), []byte("blue")); err != nil {
1444 log.Fatal(err)
1445 }
1446 if err = b.Put([]byte("abby"), []byte("red")); err != nil {
1447 log.Fatal(err)
1448 }
1449 if err = b.Put([]byte("zephyr"), []byte("purple")); err != nil {
1450 log.Fatal(err)
1451 }
1452 if err = tx.Commit(); err != nil {
1453 log.Fatal(err)
1454 }
1455
1456
1457 tx, err = db.Begin(false)
1458 if err != nil {
1459 log.Fatal(err)
1460 }
1461 c := tx.Bucket([]byte("widgets")).Cursor()
1462 for k, v := c.First(); k != nil; k, v = c.Next() {
1463 fmt.Printf("%s likes %s\n", k, v)
1464 }
1465
1466 if err = tx.Rollback(); err != nil {
1467 log.Fatal(err)
1468 }
1469
1470 if err = db.Close(); err != nil {
1471 log.Fatal(err)
1472 }
1473
1474
1475
1476
1477
1478 }
1479
1480 func BenchmarkDBBatchAutomatic(b *testing.B) {
1481 db := btesting.MustCreateDB(b)
1482
1483 if err := db.Update(func(tx *bolt.Tx) error {
1484 _, err := tx.CreateBucket([]byte("bench"))
1485 return err
1486 }); err != nil {
1487 b.Fatal(err)
1488 }
1489
1490 b.ResetTimer()
1491 for i := 0; i < b.N; i++ {
1492 start := make(chan struct{})
1493 var wg sync.WaitGroup
1494
1495 for round := 0; round < 1000; round++ {
1496 wg.Add(1)
1497
1498 go func(id uint32) {
1499 defer wg.Done()
1500 <-start
1501
1502 h := fnv.New32a()
1503 buf := make([]byte, 4)
1504 binary.LittleEndian.PutUint32(buf, id)
1505 _, _ = h.Write(buf[:])
1506 k := h.Sum(nil)
1507 insert := func(tx *bolt.Tx) error {
1508 b := tx.Bucket([]byte("bench"))
1509 return b.Put(k, []byte("filler"))
1510 }
1511 if err := db.Batch(insert); err != nil {
1512 b.Error(err)
1513 return
1514 }
1515 }(uint32(round))
1516 }
1517 close(start)
1518 wg.Wait()
1519 }
1520
1521 b.StopTimer()
1522 validateBatchBench(b, db)
1523 }
1524
1525 func BenchmarkDBBatchSingle(b *testing.B) {
1526 db := btesting.MustCreateDB(b)
1527 if err := db.Update(func(tx *bolt.Tx) error {
1528 _, err := tx.CreateBucket([]byte("bench"))
1529 return err
1530 }); err != nil {
1531 b.Fatal(err)
1532 }
1533
1534 b.ResetTimer()
1535 for i := 0; i < b.N; i++ {
1536 start := make(chan struct{})
1537 var wg sync.WaitGroup
1538
1539 for round := 0; round < 1000; round++ {
1540 wg.Add(1)
1541 go func(id uint32) {
1542 defer wg.Done()
1543 <-start
1544
1545 h := fnv.New32a()
1546 buf := make([]byte, 4)
1547 binary.LittleEndian.PutUint32(buf, id)
1548 _, _ = h.Write(buf[:])
1549 k := h.Sum(nil)
1550 insert := func(tx *bolt.Tx) error {
1551 b := tx.Bucket([]byte("bench"))
1552 return b.Put(k, []byte("filler"))
1553 }
1554 if err := db.Update(insert); err != nil {
1555 b.Error(err)
1556 return
1557 }
1558 }(uint32(round))
1559 }
1560 close(start)
1561 wg.Wait()
1562 }
1563
1564 b.StopTimer()
1565 validateBatchBench(b, db)
1566 }
1567
1568 func BenchmarkDBBatchManual10x100(b *testing.B) {
1569 db := btesting.MustCreateDB(b)
1570 if err := db.Update(func(tx *bolt.Tx) error {
1571 _, err := tx.CreateBucket([]byte("bench"))
1572 return err
1573 }); err != nil {
1574 b.Fatal(err)
1575 }
1576
1577 b.ResetTimer()
1578 for i := 0; i < b.N; i++ {
1579 start := make(chan struct{})
1580 var wg sync.WaitGroup
1581 errCh := make(chan error, 10)
1582
1583 for major := 0; major < 10; major++ {
1584 wg.Add(1)
1585 go func(id uint32) {
1586 defer wg.Done()
1587 <-start
1588
1589 insert100 := func(tx *bolt.Tx) error {
1590 h := fnv.New32a()
1591 buf := make([]byte, 4)
1592 for minor := uint32(0); minor < 100; minor++ {
1593 binary.LittleEndian.PutUint32(buf, uint32(id*100+minor))
1594 h.Reset()
1595 _, _ = h.Write(buf[:])
1596 k := h.Sum(nil)
1597 b := tx.Bucket([]byte("bench"))
1598 if err := b.Put(k, []byte("filler")); err != nil {
1599 return err
1600 }
1601 }
1602 return nil
1603 }
1604 err := db.Update(insert100)
1605 errCh <- err
1606 }(uint32(major))
1607 }
1608 close(start)
1609 wg.Wait()
1610 close(errCh)
1611 for err := range errCh {
1612 if err != nil {
1613 b.Fatal(err)
1614 }
1615 }
1616 }
1617
1618 b.StopTimer()
1619 validateBatchBench(b, db)
1620 }
1621
1622 func validateBatchBench(b *testing.B, db *btesting.DB) {
1623 var rollback = errors.New("sentinel error to cause rollback")
1624 validate := func(tx *bolt.Tx) error {
1625 bucket := tx.Bucket([]byte("bench"))
1626 h := fnv.New32a()
1627 buf := make([]byte, 4)
1628 for id := uint32(0); id < 1000; id++ {
1629 binary.LittleEndian.PutUint32(buf, id)
1630 h.Reset()
1631 _, _ = h.Write(buf[:])
1632 k := h.Sum(nil)
1633 v := bucket.Get(k)
1634 if v == nil {
1635 b.Errorf("not found id=%d key=%x", id, k)
1636 continue
1637 }
1638 if g, e := v, []byte("filler"); !bytes.Equal(g, e) {
1639 b.Errorf("bad value for id=%d key=%x: %s != %q", id, k, g, e)
1640 }
1641 if err := bucket.Delete(k); err != nil {
1642 return err
1643 }
1644 }
1645
1646 c := bucket.Cursor()
1647 for k, v := c.First(); k != nil; k, v = c.Next() {
1648 b.Errorf("unexpected key: %x = %q", k, v)
1649 }
1650 return rollback
1651 }
1652 if err := db.Update(validate); err != nil && err != rollback {
1653 b.Error(err)
1654 }
1655 }
1656
1657
1658 func tempfile() string {
1659 f, err := os.CreateTemp("", "bolt-")
1660 if err != nil {
1661 panic(err)
1662 }
1663 if err := f.Close(); err != nil {
1664 panic(err)
1665 }
1666 if err := os.Remove(f.Name()); err != nil {
1667 panic(err)
1668 }
1669 return f.Name()
1670 }
1671
1672 func trunc(b []byte, length int) []byte {
1673 if length < len(b) {
1674 return b[:length]
1675 }
1676 return b
1677 }
1678
1679 func fileSize(path string) int64 {
1680 fi, err := os.Stat(path)
1681 if err != nil {
1682 return 0
1683 }
1684 return fi.Size()
1685 }
1686
1687
1688 func u64tob(v uint64) []byte {
1689 b := make([]byte, 8)
1690 binary.BigEndian.PutUint64(b, v)
1691 return b
1692 }
1693
View as plain text