1package bbolt_test 2 3import ( 4 "bytes" 5 "encoding/binary" 6 "fmt" 7 "log" 8 "os" 9 "reflect" 10 "sort" 11 "testing" 12 "testing/quick" 13 14 bolt "go.etcd.io/bbolt" 15) 16 17// Ensure that a cursor can return a reference to the bucket that created it. 18func TestCursor_Bucket(t *testing.T) { 19 db := MustOpenDB() 20 defer db.MustClose() 21 if err := db.Update(func(tx *bolt.Tx) error { 22 b, err := tx.CreateBucket([]byte("widgets")) 23 if err != nil { 24 t.Fatal(err) 25 } 26 if cb := b.Cursor().Bucket(); !reflect.DeepEqual(cb, b) { 27 t.Fatal("cursor bucket mismatch") 28 } 29 return nil 30 }); err != nil { 31 t.Fatal(err) 32 } 33} 34 35// Ensure that a Tx cursor can seek to the appropriate keys. 36func TestCursor_Seek(t *testing.T) { 37 db := MustOpenDB() 38 defer db.MustClose() 39 if err := db.Update(func(tx *bolt.Tx) error { 40 b, err := tx.CreateBucket([]byte("widgets")) 41 if err != nil { 42 t.Fatal(err) 43 } 44 if err := b.Put([]byte("foo"), []byte("0001")); err != nil { 45 t.Fatal(err) 46 } 47 if err := b.Put([]byte("bar"), []byte("0002")); err != nil { 48 t.Fatal(err) 49 } 50 if err := b.Put([]byte("baz"), []byte("0003")); err != nil { 51 t.Fatal(err) 52 } 53 54 if _, err := b.CreateBucket([]byte("bkt")); err != nil { 55 t.Fatal(err) 56 } 57 return nil 58 }); err != nil { 59 t.Fatal(err) 60 } 61 62 if err := db.View(func(tx *bolt.Tx) error { 63 c := tx.Bucket([]byte("widgets")).Cursor() 64 65 // Exact match should go to the key. 66 if k, v := c.Seek([]byte("bar")); !bytes.Equal(k, []byte("bar")) { 67 t.Fatalf("unexpected key: %v", k) 68 } else if !bytes.Equal(v, []byte("0002")) { 69 t.Fatalf("unexpected value: %v", v) 70 } 71 72 // Inexact match should go to the next key. 73 if k, v := c.Seek([]byte("bas")); !bytes.Equal(k, []byte("baz")) { 74 t.Fatalf("unexpected key: %v", k) 75 } else if !bytes.Equal(v, []byte("0003")) { 76 t.Fatalf("unexpected value: %v", v) 77 } 78 79 // Low key should go to the first key. 80 if k, v := c.Seek([]byte("")); !bytes.Equal(k, []byte("bar")) { 81 t.Fatalf("unexpected key: %v", k) 82 } else if !bytes.Equal(v, []byte("0002")) { 83 t.Fatalf("unexpected value: %v", v) 84 } 85 86 // High key should return no key. 87 if k, v := c.Seek([]byte("zzz")); k != nil { 88 t.Fatalf("expected nil key: %v", k) 89 } else if v != nil { 90 t.Fatalf("expected nil value: %v", v) 91 } 92 93 // Buckets should return their key but no value. 94 if k, v := c.Seek([]byte("bkt")); !bytes.Equal(k, []byte("bkt")) { 95 t.Fatalf("unexpected key: %v", k) 96 } else if v != nil { 97 t.Fatalf("expected nil value: %v", v) 98 } 99 100 return nil 101 }); err != nil { 102 t.Fatal(err) 103 } 104} 105 106func TestCursor_Delete(t *testing.T) { 107 db := MustOpenDB() 108 defer db.MustClose() 109 110 const count = 1000 111 112 // Insert every other key between 0 and $count. 113 if err := db.Update(func(tx *bolt.Tx) error { 114 b, err := tx.CreateBucket([]byte("widgets")) 115 if err != nil { 116 t.Fatal(err) 117 } 118 for i := 0; i < count; i += 1 { 119 k := make([]byte, 8) 120 binary.BigEndian.PutUint64(k, uint64(i)) 121 if err := b.Put(k, make([]byte, 100)); err != nil { 122 t.Fatal(err) 123 } 124 } 125 if _, err := b.CreateBucket([]byte("sub")); err != nil { 126 t.Fatal(err) 127 } 128 return nil 129 }); err != nil { 130 t.Fatal(err) 131 } 132 133 if err := db.Update(func(tx *bolt.Tx) error { 134 c := tx.Bucket([]byte("widgets")).Cursor() 135 bound := make([]byte, 8) 136 binary.BigEndian.PutUint64(bound, uint64(count/2)) 137 for key, _ := c.First(); bytes.Compare(key, bound) < 0; key, _ = c.Next() { 138 if err := c.Delete(); err != nil { 139 t.Fatal(err) 140 } 141 } 142 143 c.Seek([]byte("sub")) 144 if err := c.Delete(); err != bolt.ErrIncompatibleValue { 145 t.Fatalf("unexpected error: %s", err) 146 } 147 148 return nil 149 }); err != nil { 150 t.Fatal(err) 151 } 152 153 if err := db.View(func(tx *bolt.Tx) error { 154 stats := tx.Bucket([]byte("widgets")).Stats() 155 if stats.KeyN != count/2+1 { 156 t.Fatalf("unexpected KeyN: %d", stats.KeyN) 157 } 158 return nil 159 }); err != nil { 160 t.Fatal(err) 161 } 162} 163 164// Ensure that a Tx cursor can seek to the appropriate keys when there are a 165// large number of keys. This test also checks that seek will always move 166// forward to the next key. 167// 168// Related: https://github.com/boltdb/bolt/pull/187 169func TestCursor_Seek_Large(t *testing.T) { 170 db := MustOpenDB() 171 defer db.MustClose() 172 173 var count = 10000 174 175 // Insert every other key between 0 and $count. 176 if err := db.Update(func(tx *bolt.Tx) error { 177 b, err := tx.CreateBucket([]byte("widgets")) 178 if err != nil { 179 t.Fatal(err) 180 } 181 182 for i := 0; i < count; i += 100 { 183 for j := i; j < i+100; j += 2 { 184 k := make([]byte, 8) 185 binary.BigEndian.PutUint64(k, uint64(j)) 186 if err := b.Put(k, make([]byte, 100)); err != nil { 187 t.Fatal(err) 188 } 189 } 190 } 191 return nil 192 }); err != nil { 193 t.Fatal(err) 194 } 195 196 if err := db.View(func(tx *bolt.Tx) error { 197 c := tx.Bucket([]byte("widgets")).Cursor() 198 for i := 0; i < count; i++ { 199 seek := make([]byte, 8) 200 binary.BigEndian.PutUint64(seek, uint64(i)) 201 202 k, _ := c.Seek(seek) 203 204 // The last seek is beyond the end of the the range so 205 // it should return nil. 206 if i == count-1 { 207 if k != nil { 208 t.Fatal("expected nil key") 209 } 210 continue 211 } 212 213 // Otherwise we should seek to the exact key or the next key. 214 num := binary.BigEndian.Uint64(k) 215 if i%2 == 0 { 216 if num != uint64(i) { 217 t.Fatalf("unexpected num: %d", num) 218 } 219 } else { 220 if num != uint64(i+1) { 221 t.Fatalf("unexpected num: %d", num) 222 } 223 } 224 } 225 226 return nil 227 }); err != nil { 228 t.Fatal(err) 229 } 230} 231 232// Ensure that a cursor can iterate over an empty bucket without error. 233func TestCursor_EmptyBucket(t *testing.T) { 234 db := MustOpenDB() 235 defer db.MustClose() 236 if err := db.Update(func(tx *bolt.Tx) error { 237 _, err := tx.CreateBucket([]byte("widgets")) 238 return err 239 }); err != nil { 240 t.Fatal(err) 241 } 242 243 if err := db.View(func(tx *bolt.Tx) error { 244 c := tx.Bucket([]byte("widgets")).Cursor() 245 k, v := c.First() 246 if k != nil { 247 t.Fatalf("unexpected key: %v", k) 248 } else if v != nil { 249 t.Fatalf("unexpected value: %v", v) 250 } 251 return nil 252 }); err != nil { 253 t.Fatal(err) 254 } 255} 256 257// Ensure that a Tx cursor can reverse iterate over an empty bucket without error. 258func TestCursor_EmptyBucketReverse(t *testing.T) { 259 db := MustOpenDB() 260 defer db.MustClose() 261 262 if err := db.Update(func(tx *bolt.Tx) error { 263 _, err := tx.CreateBucket([]byte("widgets")) 264 return err 265 }); err != nil { 266 t.Fatal(err) 267 } 268 if err := db.View(func(tx *bolt.Tx) error { 269 c := tx.Bucket([]byte("widgets")).Cursor() 270 k, v := c.Last() 271 if k != nil { 272 t.Fatalf("unexpected key: %v", k) 273 } else if v != nil { 274 t.Fatalf("unexpected value: %v", v) 275 } 276 return nil 277 }); err != nil { 278 t.Fatal(err) 279 } 280} 281 282// Ensure that a Tx cursor can iterate over a single root with a couple elements. 283func TestCursor_Iterate_Leaf(t *testing.T) { 284 db := MustOpenDB() 285 defer db.MustClose() 286 287 if err := db.Update(func(tx *bolt.Tx) error { 288 b, err := tx.CreateBucket([]byte("widgets")) 289 if err != nil { 290 t.Fatal(err) 291 } 292 if err := b.Put([]byte("baz"), []byte{}); err != nil { 293 t.Fatal(err) 294 } 295 if err := b.Put([]byte("foo"), []byte{0}); err != nil { 296 t.Fatal(err) 297 } 298 if err := b.Put([]byte("bar"), []byte{1}); err != nil { 299 t.Fatal(err) 300 } 301 return nil 302 }); err != nil { 303 t.Fatal(err) 304 } 305 tx, err := db.Begin(false) 306 if err != nil { 307 t.Fatal(err) 308 } 309 defer func() { _ = tx.Rollback() }() 310 311 c := tx.Bucket([]byte("widgets")).Cursor() 312 313 k, v := c.First() 314 if !bytes.Equal(k, []byte("bar")) { 315 t.Fatalf("unexpected key: %v", k) 316 } else if !bytes.Equal(v, []byte{1}) { 317 t.Fatalf("unexpected value: %v", v) 318 } 319 320 k, v = c.Next() 321 if !bytes.Equal(k, []byte("baz")) { 322 t.Fatalf("unexpected key: %v", k) 323 } else if !bytes.Equal(v, []byte{}) { 324 t.Fatalf("unexpected value: %v", v) 325 } 326 327 k, v = c.Next() 328 if !bytes.Equal(k, []byte("foo")) { 329 t.Fatalf("unexpected key: %v", k) 330 } else if !bytes.Equal(v, []byte{0}) { 331 t.Fatalf("unexpected value: %v", v) 332 } 333 334 k, v = c.Next() 335 if k != nil { 336 t.Fatalf("expected nil key: %v", k) 337 } else if v != nil { 338 t.Fatalf("expected nil value: %v", v) 339 } 340 341 k, v = c.Next() 342 if k != nil { 343 t.Fatalf("expected nil key: %v", k) 344 } else if v != nil { 345 t.Fatalf("expected nil value: %v", v) 346 } 347 348 if err := tx.Rollback(); err != nil { 349 t.Fatal(err) 350 } 351} 352 353// Ensure that a Tx cursor can iterate in reverse over a single root with a couple elements. 354func TestCursor_LeafRootReverse(t *testing.T) { 355 db := MustOpenDB() 356 defer db.MustClose() 357 358 if err := db.Update(func(tx *bolt.Tx) error { 359 b, err := tx.CreateBucket([]byte("widgets")) 360 if err != nil { 361 t.Fatal(err) 362 } 363 if err := b.Put([]byte("baz"), []byte{}); err != nil { 364 t.Fatal(err) 365 } 366 if err := b.Put([]byte("foo"), []byte{0}); err != nil { 367 t.Fatal(err) 368 } 369 if err := b.Put([]byte("bar"), []byte{1}); err != nil { 370 t.Fatal(err) 371 } 372 return nil 373 }); err != nil { 374 t.Fatal(err) 375 } 376 tx, err := db.Begin(false) 377 if err != nil { 378 t.Fatal(err) 379 } 380 c := tx.Bucket([]byte("widgets")).Cursor() 381 382 if k, v := c.Last(); !bytes.Equal(k, []byte("foo")) { 383 t.Fatalf("unexpected key: %v", k) 384 } else if !bytes.Equal(v, []byte{0}) { 385 t.Fatalf("unexpected value: %v", v) 386 } 387 388 if k, v := c.Prev(); !bytes.Equal(k, []byte("baz")) { 389 t.Fatalf("unexpected key: %v", k) 390 } else if !bytes.Equal(v, []byte{}) { 391 t.Fatalf("unexpected value: %v", v) 392 } 393 394 if k, v := c.Prev(); !bytes.Equal(k, []byte("bar")) { 395 t.Fatalf("unexpected key: %v", k) 396 } else if !bytes.Equal(v, []byte{1}) { 397 t.Fatalf("unexpected value: %v", v) 398 } 399 400 if k, v := c.Prev(); k != nil { 401 t.Fatalf("expected nil key: %v", k) 402 } else if v != nil { 403 t.Fatalf("expected nil value: %v", v) 404 } 405 406 if k, v := c.Prev(); k != nil { 407 t.Fatalf("expected nil key: %v", k) 408 } else if v != nil { 409 t.Fatalf("expected nil value: %v", v) 410 } 411 412 if err := tx.Rollback(); err != nil { 413 t.Fatal(err) 414 } 415} 416 417// Ensure that a Tx cursor can restart from the beginning. 418func TestCursor_Restart(t *testing.T) { 419 db := MustOpenDB() 420 defer db.MustClose() 421 422 if err := db.Update(func(tx *bolt.Tx) error { 423 b, err := tx.CreateBucket([]byte("widgets")) 424 if err != nil { 425 t.Fatal(err) 426 } 427 if err := b.Put([]byte("bar"), []byte{}); err != nil { 428 t.Fatal(err) 429 } 430 if err := b.Put([]byte("foo"), []byte{}); err != nil { 431 t.Fatal(err) 432 } 433 return nil 434 }); err != nil { 435 t.Fatal(err) 436 } 437 438 tx, err := db.Begin(false) 439 if err != nil { 440 t.Fatal(err) 441 } 442 c := tx.Bucket([]byte("widgets")).Cursor() 443 444 if k, _ := c.First(); !bytes.Equal(k, []byte("bar")) { 445 t.Fatalf("unexpected key: %v", k) 446 } 447 if k, _ := c.Next(); !bytes.Equal(k, []byte("foo")) { 448 t.Fatalf("unexpected key: %v", k) 449 } 450 451 if k, _ := c.First(); !bytes.Equal(k, []byte("bar")) { 452 t.Fatalf("unexpected key: %v", k) 453 } 454 if k, _ := c.Next(); !bytes.Equal(k, []byte("foo")) { 455 t.Fatalf("unexpected key: %v", k) 456 } 457 458 if err := tx.Rollback(); err != nil { 459 t.Fatal(err) 460 } 461} 462 463// Ensure that a cursor can skip over empty pages that have been deleted. 464func TestCursor_First_EmptyPages(t *testing.T) { 465 db := MustOpenDB() 466 defer db.MustClose() 467 468 // Create 1000 keys in the "widgets" bucket. 469 if err := db.Update(func(tx *bolt.Tx) error { 470 b, err := tx.CreateBucket([]byte("widgets")) 471 if err != nil { 472 t.Fatal(err) 473 } 474 475 for i := 0; i < 1000; i++ { 476 if err := b.Put(u64tob(uint64(i)), []byte{}); err != nil { 477 t.Fatal(err) 478 } 479 } 480 481 return nil 482 }); err != nil { 483 t.Fatal(err) 484 } 485 486 // Delete half the keys and then try to iterate. 487 if err := db.Update(func(tx *bolt.Tx) error { 488 b := tx.Bucket([]byte("widgets")) 489 for i := 0; i < 600; i++ { 490 if err := b.Delete(u64tob(uint64(i))); err != nil { 491 t.Fatal(err) 492 } 493 } 494 495 c := b.Cursor() 496 var n int 497 for k, _ := c.First(); k != nil; k, _ = c.Next() { 498 n++ 499 } 500 if n != 400 { 501 t.Fatalf("unexpected key count: %d", n) 502 } 503 504 return nil 505 }); err != nil { 506 t.Fatal(err) 507 } 508} 509 510// Ensure that a Tx can iterate over all elements in a bucket. 511func TestCursor_QuickCheck(t *testing.T) { 512 f := func(items testdata) bool { 513 db := MustOpenDB() 514 defer db.MustClose() 515 516 // Bulk insert all values. 517 tx, err := db.Begin(true) 518 if err != nil { 519 t.Fatal(err) 520 } 521 b, err := tx.CreateBucket([]byte("widgets")) 522 if err != nil { 523 t.Fatal(err) 524 } 525 for _, item := range items { 526 if err := b.Put(item.Key, item.Value); err != nil { 527 t.Fatal(err) 528 } 529 } 530 if err := tx.Commit(); err != nil { 531 t.Fatal(err) 532 } 533 534 // Sort test data. 535 sort.Sort(items) 536 537 // Iterate over all items and check consistency. 538 var index = 0 539 tx, err = db.Begin(false) 540 if err != nil { 541 t.Fatal(err) 542 } 543 544 c := tx.Bucket([]byte("widgets")).Cursor() 545 for k, v := c.First(); k != nil && index < len(items); k, v = c.Next() { 546 if !bytes.Equal(k, items[index].Key) { 547 t.Fatalf("unexpected key: %v", k) 548 } else if !bytes.Equal(v, items[index].Value) { 549 t.Fatalf("unexpected value: %v", v) 550 } 551 index++ 552 } 553 if len(items) != index { 554 t.Fatalf("unexpected item count: %v, expected %v", len(items), index) 555 } 556 557 if err := tx.Rollback(); err != nil { 558 t.Fatal(err) 559 } 560 561 return true 562 } 563 if err := quick.Check(f, qconfig()); err != nil { 564 t.Error(err) 565 } 566} 567 568// Ensure that a transaction can iterate over all elements in a bucket in reverse. 569func TestCursor_QuickCheck_Reverse(t *testing.T) { 570 f := func(items testdata) bool { 571 db := MustOpenDB() 572 defer db.MustClose() 573 574 // Bulk insert all values. 575 tx, err := db.Begin(true) 576 if err != nil { 577 t.Fatal(err) 578 } 579 b, err := tx.CreateBucket([]byte("widgets")) 580 if err != nil { 581 t.Fatal(err) 582 } 583 for _, item := range items { 584 if err := b.Put(item.Key, item.Value); err != nil { 585 t.Fatal(err) 586 } 587 } 588 if err := tx.Commit(); err != nil { 589 t.Fatal(err) 590 } 591 592 // Sort test data. 593 sort.Sort(revtestdata(items)) 594 595 // Iterate over all items and check consistency. 596 var index = 0 597 tx, err = db.Begin(false) 598 if err != nil { 599 t.Fatal(err) 600 } 601 c := tx.Bucket([]byte("widgets")).Cursor() 602 for k, v := c.Last(); k != nil && index < len(items); k, v = c.Prev() { 603 if !bytes.Equal(k, items[index].Key) { 604 t.Fatalf("unexpected key: %v", k) 605 } else if !bytes.Equal(v, items[index].Value) { 606 t.Fatalf("unexpected value: %v", v) 607 } 608 index++ 609 } 610 if len(items) != index { 611 t.Fatalf("unexpected item count: %v, expected %v", len(items), index) 612 } 613 614 if err := tx.Rollback(); err != nil { 615 t.Fatal(err) 616 } 617 618 return true 619 } 620 if err := quick.Check(f, qconfig()); err != nil { 621 t.Error(err) 622 } 623} 624 625// Ensure that a Tx cursor can iterate over subbuckets. 626func TestCursor_QuickCheck_BucketsOnly(t *testing.T) { 627 db := MustOpenDB() 628 defer db.MustClose() 629 630 if err := db.Update(func(tx *bolt.Tx) error { 631 b, err := tx.CreateBucket([]byte("widgets")) 632 if err != nil { 633 t.Fatal(err) 634 } 635 if _, err := b.CreateBucket([]byte("foo")); err != nil { 636 t.Fatal(err) 637 } 638 if _, err := b.CreateBucket([]byte("bar")); err != nil { 639 t.Fatal(err) 640 } 641 if _, err := b.CreateBucket([]byte("baz")); err != nil { 642 t.Fatal(err) 643 } 644 return nil 645 }); err != nil { 646 t.Fatal(err) 647 } 648 649 if err := db.View(func(tx *bolt.Tx) error { 650 var names []string 651 c := tx.Bucket([]byte("widgets")).Cursor() 652 for k, v := c.First(); k != nil; k, v = c.Next() { 653 names = append(names, string(k)) 654 if v != nil { 655 t.Fatalf("unexpected value: %v", v) 656 } 657 } 658 if !reflect.DeepEqual(names, []string{"bar", "baz", "foo"}) { 659 t.Fatalf("unexpected names: %+v", names) 660 } 661 return nil 662 }); err != nil { 663 t.Fatal(err) 664 } 665} 666 667// Ensure that a Tx cursor can reverse iterate over subbuckets. 668func TestCursor_QuickCheck_BucketsOnly_Reverse(t *testing.T) { 669 db := MustOpenDB() 670 defer db.MustClose() 671 672 if err := db.Update(func(tx *bolt.Tx) error { 673 b, err := tx.CreateBucket([]byte("widgets")) 674 if err != nil { 675 t.Fatal(err) 676 } 677 if _, err := b.CreateBucket([]byte("foo")); err != nil { 678 t.Fatal(err) 679 } 680 if _, err := b.CreateBucket([]byte("bar")); err != nil { 681 t.Fatal(err) 682 } 683 if _, err := b.CreateBucket([]byte("baz")); err != nil { 684 t.Fatal(err) 685 } 686 return nil 687 }); err != nil { 688 t.Fatal(err) 689 } 690 691 if err := db.View(func(tx *bolt.Tx) error { 692 var names []string 693 c := tx.Bucket([]byte("widgets")).Cursor() 694 for k, v := c.Last(); k != nil; k, v = c.Prev() { 695 names = append(names, string(k)) 696 if v != nil { 697 t.Fatalf("unexpected value: %v", v) 698 } 699 } 700 if !reflect.DeepEqual(names, []string{"foo", "baz", "bar"}) { 701 t.Fatalf("unexpected names: %+v", names) 702 } 703 return nil 704 }); err != nil { 705 t.Fatal(err) 706 } 707} 708 709func ExampleCursor() { 710 // Open the database. 711 db, err := bolt.Open(tempfile(), 0666, nil) 712 if err != nil { 713 log.Fatal(err) 714 } 715 defer os.Remove(db.Path()) 716 717 // Start a read-write transaction. 718 if err := db.Update(func(tx *bolt.Tx) error { 719 // Create a new bucket. 720 b, err := tx.CreateBucket([]byte("animals")) 721 if err != nil { 722 return err 723 } 724 725 // Insert data into a bucket. 726 if err := b.Put([]byte("dog"), []byte("fun")); err != nil { 727 log.Fatal(err) 728 } 729 if err := b.Put([]byte("cat"), []byte("lame")); err != nil { 730 log.Fatal(err) 731 } 732 if err := b.Put([]byte("liger"), []byte("awesome")); err != nil { 733 log.Fatal(err) 734 } 735 736 // Create a cursor for iteration. 737 c := b.Cursor() 738 739 // Iterate over items in sorted key order. This starts from the 740 // first key/value pair and updates the k/v variables to the 741 // next key/value on each iteration. 742 // 743 // The loop finishes at the end of the cursor when a nil key is returned. 744 for k, v := c.First(); k != nil; k, v = c.Next() { 745 fmt.Printf("A %s is %s.\n", k, v) 746 } 747 748 return nil 749 }); err != nil { 750 log.Fatal(err) 751 } 752 753 if err := db.Close(); err != nil { 754 log.Fatal(err) 755 } 756 757 // Output: 758 // A cat is lame. 759 // A dog is fun. 760 // A liger is awesome. 761} 762 763func ExampleCursor_reverse() { 764 // Open the database. 765 db, err := bolt.Open(tempfile(), 0666, nil) 766 if err != nil { 767 log.Fatal(err) 768 } 769 defer os.Remove(db.Path()) 770 771 // Start a read-write transaction. 772 if err := db.Update(func(tx *bolt.Tx) error { 773 // Create a new bucket. 774 b, err := tx.CreateBucket([]byte("animals")) 775 if err != nil { 776 return err 777 } 778 779 // Insert data into a bucket. 780 if err := b.Put([]byte("dog"), []byte("fun")); err != nil { 781 log.Fatal(err) 782 } 783 if err := b.Put([]byte("cat"), []byte("lame")); err != nil { 784 log.Fatal(err) 785 } 786 if err := b.Put([]byte("liger"), []byte("awesome")); err != nil { 787 log.Fatal(err) 788 } 789 790 // Create a cursor for iteration. 791 c := b.Cursor() 792 793 // Iterate over items in reverse sorted key order. This starts 794 // from the last key/value pair and updates the k/v variables to 795 // the previous key/value on each iteration. 796 // 797 // The loop finishes at the beginning of the cursor when a nil key 798 // is returned. 799 for k, v := c.Last(); k != nil; k, v = c.Prev() { 800 fmt.Printf("A %s is %s.\n", k, v) 801 } 802 803 return nil 804 }); err != nil { 805 log.Fatal(err) 806 } 807 808 // Close the database to release the file lock. 809 if err := db.Close(); err != nil { 810 log.Fatal(err) 811 } 812 813 // Output: 814 // A liger is awesome. 815 // A dog is fun. 816 // A cat is lame. 817} 818