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