package buntdb import ( "bytes" "errors" "fmt" "io/ioutil" "math/rand" "os" "strconv" "strings" "sync" "testing" "time" "github.com/tidwall/assert" "github.com/tidwall/lotsa" ) func testOpen(t testing.TB) *DB { if err := os.RemoveAll("data.db"); err != nil { t.Fatal(err) } return testReOpen(t, nil) } func testReOpen(t testing.TB, db *DB) *DB { return testReOpenDelay(t, db, 0) } func testReOpenDelay(t testing.TB, db *DB, dur time.Duration) *DB { if db != nil { if err := db.Close(); err != nil { t.Fatal(err) } } time.Sleep(dur) db, err := Open("data.db") if err != nil { t.Fatal(err) } return db } func testClose(db *DB) { _ = db.Close() _ = os.RemoveAll("data.db") } func TestBackgroudOperations(t *testing.T) { db := testOpen(t) defer testClose(db) for i := 0; i < 1000; i++ { if err := db.Update(func(tx *Tx) error { for j := 0; j < 200; j++ { if _, _, err := tx.Set(fmt.Sprintf("hello%d", j), "planet", nil); err != nil { return err } } if _, _, err := tx.Set("hi", "world", &SetOptions{Expires: true, TTL: time.Second / 2}); err != nil { return err } return nil }); err != nil { t.Fatal(err) } } n := 0 err := db.View(func(tx *Tx) error { var err error n, err = tx.Len() return err }) if err != nil { t.Fatal(err) } if n != 201 { t.Fatalf("expecting '%v', got '%v'", 201, n) } time.Sleep(time.Millisecond * 1500) db = testReOpen(t, db) defer testClose(db) n = 0 err = db.View(func(tx *Tx) error { var err error n, err = tx.Len() return err }) if err != nil { t.Fatal(err) } if n != 200 { t.Fatalf("expecting '%v', got '%v'", 200, n) } } func TestSaveLoad(t *testing.T) { db, _ := Open(":memory:") defer db.Close() if err := db.Update(func(tx *Tx) error { for i := 0; i < 20; i++ { _, _, err := tx.Set(fmt.Sprintf("key:%d", i), fmt.Sprintf("planet:%d", i), nil) if err != nil { return err } } return nil }); err != nil { t.Fatal(err) } f, err := os.Create("temp.db") if err != nil { t.Fatal(err) } defer func() { f.Close() os.RemoveAll("temp.db") }() if err := db.Save(f); err != nil { t.Fatal(err) } if err := f.Close(); err != nil { t.Fatal(err) } db.Close() db, _ = Open(":memory:") defer db.Close() f, err = os.Open("temp.db") if err != nil { t.Fatal(err) } defer f.Close() if err := db.Load(f); err != nil { t.Fatal(err) } if err := db.View(func(tx *Tx) error { for i := 0; i < 20; i++ { ex := fmt.Sprintf("planet:%d", i) val, err := tx.Get(fmt.Sprintf("key:%d", i)) if err != nil { return err } if ex != val { t.Fatalf("expected %s, got %s", ex, val) } } return nil }); err != nil { t.Fatal(err) } } func TestMutatingIterator(t *testing.T) { db := testOpen(t) defer testClose(db) count := 1000 if err := db.CreateIndex("ages", "user:*:age", IndexInt); err != nil { t.Fatal(err) } for i := 0; i < 10; i++ { if err := db.Update(func(tx *Tx) error { for j := 0; j < count; j++ { key := fmt.Sprintf("user:%d:age", j) val := fmt.Sprintf("%d", rand.Intn(100)) if _, _, err := tx.Set(key, val, nil); err != nil { return err } } return nil }); err != nil { t.Fatal(err) } if err := db.Update(func(tx *Tx) error { return tx.Ascend("ages", func(key, val string) bool { _, err := tx.Delete(key) if err != ErrTxIterating { t.Fatal("should not be able to call Delete while iterating.") } _, _, err = tx.Set(key, "", nil) if err != ErrTxIterating { t.Fatal("should not be able to call Set while iterating.") } return true }) }); err != nil { t.Fatal(err) } } } func TestCaseInsensitiveIndex(t *testing.T) { db := testOpen(t) defer testClose(db) count := 1000 if err := db.Update(func(tx *Tx) error { opts := &IndexOptions{ CaseInsensitiveKeyMatching: true, } return tx.CreateIndexOptions("ages", "User:*:age", opts, IndexInt) }); err != nil { t.Fatal(err) } if err := db.Update(func(tx *Tx) error { for j := 0; j < count; j++ { key := fmt.Sprintf("user:%d:age", j) val := fmt.Sprintf("%d", rand.Intn(100)) if _, _, err := tx.Set(key, val, nil); err != nil { return err } } return nil }); err != nil { t.Fatal(err) } if err := db.View(func(tx *Tx) error { var vals []string err := tx.Ascend("ages", func(key, value string) bool { vals = append(vals, value) return true }) if err != nil { return err } if len(vals) != count { return fmt.Errorf("expected '%v', got '%v'", count, len(vals)) } return nil }); err != nil { t.Fatal(err) } } func TestIndexTransaction(t *testing.T) { db := testOpen(t) defer testClose(db) var errFine = errors.New("this is fine") ascend := func(tx *Tx, index string) ([]string, error) { var vals []string if err := tx.Ascend(index, func(key, val string) bool { vals = append(vals, key, val) return true }); err != nil { return nil, err } return vals, nil } ascendEqual := func(tx *Tx, index string, vals []string) error { vals2, err := ascend(tx, index) if err != nil { return err } if len(vals) != len(vals2) { return errors.New("invalid size match") } for i := 0; i < len(vals); i++ { if vals[i] != vals2[i] { return errors.New("invalid order") } } return nil } // test creating an index and adding items if err := db.Update(func(tx *Tx) error { tx.Set("1", "3", nil) tx.Set("2", "2", nil) tx.Set("3", "1", nil) if err := tx.CreateIndex("idx1", "*", IndexInt); err != nil { return err } if err := ascendEqual(tx, "idx1", []string{"3", "1", "2", "2", "1", "3"}); err != nil { return err } return nil }); err != nil { t.Fatal(err) } // test to see if the items persisted from previous transaction // test add item. // test force rollback. if err := db.Update(func(tx *Tx) error { if err := ascendEqual(tx, "idx1", []string{"3", "1", "2", "2", "1", "3"}); err != nil { return err } tx.Set("4", "0", nil) if err := ascendEqual(tx, "idx1", []string{"4", "0", "3", "1", "2", "2", "1", "3"}); err != nil { return err } return errFine }); err != errFine { t.Fatalf("expected '%v', got '%v'", errFine, err) } // test to see if the rollback happened if err := db.View(func(tx *Tx) error { if err := ascendEqual(tx, "idx1", []string{"3", "1", "2", "2", "1", "3"}); err != nil { return err } return nil }); err != nil { t.Fatalf("expected '%v', got '%v'", nil, err) } // del item, drop index, rollback if err := db.Update(func(tx *Tx) error { if err := tx.DropIndex("idx1"); err != nil { return err } return errFine }); err != errFine { t.Fatalf("expected '%v', got '%v'", errFine, err) } // test to see if the rollback happened if err := db.View(func(tx *Tx) error { if err := ascendEqual(tx, "idx1", []string{"3", "1", "2", "2", "1", "3"}); err != nil { return err } return nil }); err != nil { t.Fatalf("expected '%v', got '%v'", nil, err) } various := func(reterr error) error { // del item 3, add index 2, add item 4, test index 1 and 2. // flushdb, test index 1 and 2. // add item 1 and 2, add index 2 and 3, test index 2 and 3 return db.Update(func(tx *Tx) error { tx.Delete("3") tx.CreateIndex("idx2", "*", IndexInt) tx.Set("4", "0", nil) if err := ascendEqual(tx, "idx1", []string{"4", "0", "2", "2", "1", "3"}); err != nil { return fmt.Errorf("err: %v", err) } if err := ascendEqual(tx, "idx2", []string{"4", "0", "2", "2", "1", "3"}); err != nil { return fmt.Errorf("err: %v", err) } tx.DeleteAll() if err := ascendEqual(tx, "idx1", []string{}); err != nil { return fmt.Errorf("err: %v", err) } if err := ascendEqual(tx, "idx2", []string{}); err != nil { return fmt.Errorf("err: %v", err) } tx.Set("1", "3", nil) tx.Set("2", "2", nil) tx.CreateIndex("idx1", "*", IndexInt) tx.CreateIndex("idx2", "*", IndexInt) if err := ascendEqual(tx, "idx1", []string{"2", "2", "1", "3"}); err != nil { return fmt.Errorf("err: %v", err) } if err := ascendEqual(tx, "idx2", []string{"2", "2", "1", "3"}); err != nil { return fmt.Errorf("err: %v", err) } return reterr }) } // various rollback if err := various(errFine); err != errFine { t.Fatalf("expected '%v', got '%v'", errFine, err) } // test to see if the rollback happened if err := db.View(func(tx *Tx) error { if err := ascendEqual(tx, "idx1", []string{"3", "1", "2", "2", "1", "3"}); err != nil { return fmt.Errorf("err: %v", err) } if err := ascendEqual(tx, "idx2", []string{"3", "1", "2", "2", "1", "3"}); err != ErrNotFound { return fmt.Errorf("err: %v", err) } return nil }); err != nil { t.Fatalf("expected '%v', got '%v'", nil, err) } // various commit if err := various(nil); err != nil { t.Fatalf("expected '%v', got '%v'", nil, err) } // test to see if the commit happened if err := db.View(func(tx *Tx) error { if err := ascendEqual(tx, "idx1", []string{"2", "2", "1", "3"}); err != nil { return fmt.Errorf("err: %v", err) } if err := ascendEqual(tx, "idx2", []string{"2", "2", "1", "3"}); err != nil { return fmt.Errorf("err: %v", err) } return nil }); err != nil { t.Fatalf("expected '%v', got '%v'", nil, err) } } func TestDeleteAll(t *testing.T) { db := testOpen(t) defer testClose(db) db.Update(func(tx *Tx) error { tx.Set("hello1", "planet1", nil) tx.Set("hello2", "planet2", nil) tx.Set("hello3", "planet3", nil) return nil }) db.CreateIndex("all", "*", IndexString) db.Update(func(tx *Tx) error { tx.Set("hello1", "planet1.1", nil) tx.DeleteAll() tx.Set("bb", "11", nil) tx.Set("aa", "**", nil) tx.Delete("aa") tx.Set("aa", "22", nil) return nil }) var res string var res2 string db.View(func(tx *Tx) error { tx.Ascend("", func(key, val string) bool { res += key + ":" + val + "\n" return true }) tx.Ascend("all", func(key, val string) bool { res2 += key + ":" + val + "\n" return true }) return nil }) if res != "aa:22\nbb:11\n" { t.Fatal("fail") } if res2 != "bb:11\naa:22\n" { t.Fatal("fail") } db = testReOpen(t, db) defer testClose(db) res = "" res2 = "" db.CreateIndex("all", "*", IndexString) db.View(func(tx *Tx) error { tx.Ascend("", func(key, val string) bool { res += key + ":" + val + "\n" return true }) tx.Ascend("all", func(key, val string) bool { res2 += key + ":" + val + "\n" return true }) return nil }) if res != "aa:22\nbb:11\n" { t.Fatal("fail") } if res2 != "bb:11\naa:22\n" { t.Fatal("fail") } db.Update(func(tx *Tx) error { tx.Set("1", "1", nil) tx.Set("2", "2", nil) tx.Set("3", "3", nil) tx.Set("4", "4", nil) return nil }) err := db.Update(func(tx *Tx) error { tx.Set("1", "a", nil) tx.Set("5", "5", nil) tx.Delete("2") tx.Set("6", "6", nil) tx.DeleteAll() tx.Set("7", "7", nil) tx.Set("8", "8", nil) tx.Set("6", "c", nil) return errors.New("please rollback") }) if err == nil || err.Error() != "please rollback" { t.Fatal("expecteding 'please rollback' error") } res = "" res2 = "" db.View(func(tx *Tx) error { tx.Ascend("", func(key, val string) bool { res += key + ":" + val + "\n" return true }) tx.Ascend("all", func(key, val string) bool { res2 += key + ":" + val + "\n" return true }) return nil }) if res != "1:1\n2:2\n3:3\n4:4\naa:22\nbb:11\n" { t.Fatal("fail") } if res2 != "1:1\nbb:11\n2:2\naa:22\n3:3\n4:4\n" { t.Fatal("fail") } } func TestAscendEqual(t *testing.T) { db := testOpen(t) defer testClose(db) if err := db.Update(func(tx *Tx) error { for i := 0; i < 300; i++ { _, _, err := tx.Set(fmt.Sprintf("key:%05dA", i), fmt.Sprintf("%d", i+1000), nil) if err != nil { return err } _, _, err = tx.Set(fmt.Sprintf("key:%05dB", i), fmt.Sprintf("%d", i+1000), nil) if err != nil { return err } } return tx.CreateIndex("num", "*", IndexInt) }); err != nil { t.Fatal(err) } var res []string if err := db.View(func(tx *Tx) error { return tx.AscendEqual("", "key:00055A", func(key, value string) bool { res = append(res, key) return true }) }); err != nil { t.Fatal(err) } if len(res) != 1 { t.Fatalf("expected %v, got %v", 1, len(res)) } if res[0] != "key:00055A" { t.Fatalf("expected %v, got %v", "key:00055A", res[0]) } res = nil if err := db.View(func(tx *Tx) error { return tx.AscendEqual("num", "1125", func(key, value string) bool { res = append(res, key) return true }) }); err != nil { t.Fatal(err) } if len(res) != 2 { t.Fatalf("expected %v, got %v", 2, len(res)) } if res[0] != "key:00125A" { t.Fatalf("expected %v, got %v", "key:00125A", res[0]) } if res[1] != "key:00125B" { t.Fatalf("expected %v, got %v", "key:00125B", res[1]) } } func TestDescendEqual(t *testing.T) { db := testOpen(t) defer testClose(db) if err := db.Update(func(tx *Tx) error { for i := 0; i < 300; i++ { _, _, err := tx.Set(fmt.Sprintf("key:%05dA", i), fmt.Sprintf("%d", i+1000), nil) if err != nil { return err } _, _, err = tx.Set(fmt.Sprintf("key:%05dB", i), fmt.Sprintf("%d", i+1000), nil) if err != nil { return err } } return tx.CreateIndex("num", "*", IndexInt) }); err != nil { t.Fatal(err) } var res []string if err := db.View(func(tx *Tx) error { return tx.DescendEqual("", "key:00055A", func(key, value string) bool { res = append(res, key) return true }) }); err != nil { t.Fatal(err) } if len(res) != 1 { t.Fatalf("expected %v, got %v", 1, len(res)) } if res[0] != "key:00055A" { t.Fatalf("expected %v, got %v", "key:00055A", res[0]) } res = nil if err := db.View(func(tx *Tx) error { return tx.DescendEqual("num", "1125", func(key, value string) bool { res = append(res, key) return true }) }); err != nil { t.Fatal(err) } if len(res) != 2 { t.Fatalf("expected %v, got %v", 2, len(res)) } if res[0] != "key:00125B" { t.Fatalf("expected %v, got %v", "key:00125B", res[0]) } if res[1] != "key:00125A" { t.Fatalf("expected %v, got %v", "key:00125A", res[1]) } } func TestVariousTx(t *testing.T) { db := testOpen(t) defer testClose(db) if err := db.Update(func(tx *Tx) error { _, _, err := tx.Set("hello", "planet", nil) return err }); err != nil { t.Fatal(err) } errBroken := errors.New("broken") if err := db.Update(func(tx *Tx) error { _, _, _ = tx.Set("hello", "world", nil) return errBroken }); err != errBroken { t.Fatalf("did not correctly receive the user-defined transaction error.") } var val string err := db.View(func(tx *Tx) error { var err error val, err = tx.Get("hello") return err }) if err != nil { t.Fatal(err) } if val == "world" { t.Fatal("a rollbacked transaction got through") } if val != "planet" { t.Fatalf("expecting '%v', got '%v'", "planet", val) } if err := db.Update(func(tx *Tx) error { tx.db = nil if _, _, err := tx.Set("hello", "planet", nil); err != ErrTxClosed { t.Fatal("expecting a tx closed error") } if _, err := tx.Delete("hello"); err != ErrTxClosed { t.Fatal("expecting a tx closed error") } if _, err := tx.Get("hello"); err != ErrTxClosed { t.Fatal("expecting a tx closed error") } tx.db = db tx.writable = false if _, _, err := tx.Set("hello", "planet", nil); err != ErrTxNotWritable { t.Fatal("expecting a tx not writable error") } if _, err := tx.Delete("hello"); err != ErrTxNotWritable { t.Fatal("expecting a tx not writable error") } tx.writable = true if _, err := tx.Get("something"); err != ErrNotFound { t.Fatalf("expecting not found error") } if _, err := tx.Delete("something"); err != ErrNotFound { t.Fatalf("expecting not found error") } if _, _, err := tx.Set("var", "val", &SetOptions{Expires: true, TTL: 0}); err != nil { t.Fatal(err) } if _, err := tx.Get("var"); err != ErrNotFound { t.Fatalf("expecting not found error") } if _, err := tx.Delete("var"); err != ErrNotFound { tx.unlock() t.Fatalf("expecting not found error") } return nil }); err != nil { t.Fatal(err) } // test non-managed transactions tx, err := db.Begin(true) if err != nil { t.Fatal(err) } defer tx.Rollback() _, _, err = tx.Set("howdy", "world", nil) if err != nil { t.Fatal(err) } if err := tx.Commit(); err != nil { t.Fatal(err) } tx, err = db.Begin(false) if err != nil { t.Fatal(err) } defer tx.Rollback() v, err := tx.Get("howdy") if err != nil { t.Fatal(err) } if v != "world" { t.Fatalf("expecting '%v', got '%v'", "world", v) } if err := tx.Rollback(); err != nil { t.Fatal(err) } tx, err = db.Begin(true) if err != nil { t.Fatal(err) } defer tx.Rollback() v, err = tx.Get("howdy") if err != nil { t.Fatal(err) } if v != "world" { t.Fatalf("expecting '%v', got '%v'", "world", v) } _, err = tx.Delete("howdy") if err != nil { t.Fatal(err) } if err := tx.Commit(); err != nil { t.Fatal(err) } // test for invalid commits if err := db.Update(func(tx *Tx) error { // we are going to do some hackery defer func() { if v := recover(); v != nil { if v.(string) != "managed tx commit not allowed" { t.Fatal(v.(string)) } } }() return tx.Commit() }); err != nil { t.Fatal(err) } // test for invalid commits if err := db.Update(func(tx *Tx) error { // we are going to do some hackery defer func() { if v := recover(); v != nil { if v.(string) != "managed tx rollback not allowed" { t.Fatal(v.(string)) } } }() return tx.Rollback() }); err != nil { t.Fatal(err) } // test for closed transactions if err := db.Update(func(tx *Tx) error { tx.db = nil return nil }); err != ErrTxClosed { t.Fatal("expecting tx closed error") } db.mu.Unlock() // test for invalid writes if err := db.Update(func(tx *Tx) error { tx.writable = false return nil }); err != ErrTxNotWritable { t.Fatal("expecting tx not writable error") } db.mu.Unlock() // test for closed transactions if err := db.View(func(tx *Tx) error { tx.db = nil return nil }); err != ErrTxClosed { t.Fatal("expecting tx closed error") } db.mu.RUnlock() // flush to unwritable file if err := db.Update(func(tx *Tx) error { _, _, err := tx.Set("var1", "val1", nil) if err != nil { t.Fatal(err) } return tx.db.file.Close() }); err == nil { t.Fatal("should not be able to commit when the file is closed") } db.file, err = os.OpenFile("data.db", os.O_CREATE|os.O_RDWR, 0666) if err != nil { t.Fatal(err) } if _, err := db.file.Seek(0, 2); err != nil { t.Fatal(err) } db.buf = nil if err := db.CreateIndex("blank", "*", nil); err != nil { t.Fatal(err) } if err := db.CreateIndex("real", "*", IndexInt); err != nil { t.Fatal(err) } // test scanning if err := db.Update(func(tx *Tx) error { less, err := tx.GetLess("junk") if err != ErrNotFound { t.Fatalf("expecting a not found, got %v", err) } if less != nil { t.Fatal("expecting nil, got a less function") } less, err = tx.GetLess("blank") if err != ErrNotFound { t.Fatalf("expecting a not found, got %v", err) } if less != nil { t.Fatal("expecting nil, got a less function") } less, err = tx.GetLess("real") if err != nil { return err } if less == nil { t.Fatal("expecting a less function, got nil") } _, _, err = tx.Set("nothing", "here", nil) return err }); err != nil { t.Fatal(err) } if err := db.View(func(tx *Tx) error { s := "" err := tx.Ascend("", func(key, val string) bool { s += key + ":" + val + "\n" return true }) if err != nil { return err } if s != "hello:planet\nnothing:here\n" { t.Fatal("invalid scan") } tx.db = nil err = tx.Ascend("", func(key, val string) bool { return true }) if err != ErrTxClosed { tx.unlock() t.Fatal("expecting tx closed error") } tx.db = db err = tx.Ascend("na", func(key, val string) bool { return true }) if err != ErrNotFound { t.Fatal("expecting not found error") } err = tx.Ascend("blank", func(key, val string) bool { return true }) if err != nil { t.Fatal(err) } s = "" err = tx.AscendLessThan("", "liger", func(key, val string) bool { s += key + ":" + val + "\n" return true }) if err != nil { return err } if s != "hello:planet\n" { t.Fatal("invalid scan") } s = "" err = tx.Descend("", func(key, val string) bool { s += key + ":" + val + "\n" return true }) if err != nil { return err } if s != "nothing:here\nhello:planet\n" { t.Fatal("invalid scan") } s = "" err = tx.DescendLessOrEqual("", "liger", func(key, val string) bool { s += key + ":" + val + "\n" return true }) if err != nil { return err } if s != "hello:planet\n" { t.Fatal("invalid scan") } s = "" err = tx.DescendGreaterThan("", "liger", func(key, val string) bool { s += key + ":" + val + "\n" return true }) if err != nil { return err } if s != "nothing:here\n" { t.Fatal("invalid scan") } s = "" err = tx.DescendRange("", "liger", "apple", func(key, val string) bool { s += key + ":" + val + "\n" return true }) if err != nil { return err } if s != "hello:planet\n" { t.Fatal("invalid scan") } return nil }); err != nil { t.Fatal(err) } // test some spatial stuff if err := db.CreateSpatialIndex("spat", "rect:*", IndexRect); err != nil { t.Fatal(err) } if err := db.CreateSpatialIndex("junk", "rect:*", nil); err != nil { t.Fatal(err) } err = db.Update(func(tx *Tx) error { rect, err := tx.GetRect("spat") if err != nil { return err } if rect == nil { t.Fatal("expecting a rect function, got nil") } rect, err = tx.GetRect("junk") if err != ErrNotFound { t.Fatalf("expecting a not found, got %v", err) } if rect != nil { t.Fatal("expecting nil, got a rect function") } rect, err = tx.GetRect("na") if err != ErrNotFound { t.Fatalf("expecting a not found, got %v", err) } if rect != nil { t.Fatal("expecting nil, got a rect function") } if _, _, err := tx.Set("rect:1", "[10 10],[20 20]", nil); err != nil { return err } if _, _, err := tx.Set("rect:2", "[15 15],[25 25]", nil); err != nil { return err } if _, _, err := tx.Set("shape:1", "[12 12],[25 25]", nil); err != nil { return err } s := "" err = tx.Intersects("spat", "[5 5],[13 13]", func(key, val string) bool { s += key + ":" + val + "\n" return true }) if err != nil { return err } if s != "rect:1:[10 10],[20 20]\n" { t.Fatal("invalid scan") } tx.db = nil err = tx.Intersects("spat", "[5 5],[13 13]", func(key, val string) bool { return true }) if err != ErrTxClosed { t.Fatal("expecting tx closed error") } tx.db = db err = tx.Intersects("", "[5 5],[13 13]", func(key, val string) bool { return true }) if err != nil { t.Fatal(err) } err = tx.Intersects("na", "[5 5],[13 13]", func(key, val string) bool { return true }) if err != ErrNotFound { t.Fatal("expecting not found error") } err = tx.Intersects("junk", "[5 5],[13 13]", func(key, val string) bool { return true }) if err != nil { t.Fatal(err) } n, err := tx.Len() if err != nil { t.Fatal(err) } if n != 5 { t.Fatalf("expecting %v, got %v", 5, n) } tx.db = nil _, err = tx.Len() if err != ErrTxClosed { t.Fatal("expecting tx closed error") } tx.db = db return nil }) if err != nil { t.Fatal(err) } // test after closing if err := db.Close(); err != nil { t.Fatal(err) } if err := db.Update(func(tx *Tx) error { return nil }); err != ErrDatabaseClosed { t.Fatalf("should not be able to perform transactionso on a closed database.") } } func TestNearby(t *testing.T) { rand.Seed(time.Now().UnixNano()) N := 100000 db, _ := Open(":memory:") db.CreateSpatialIndex("points", "*", IndexRect) db.Update(func(tx *Tx) error { for i := 0; i < N; i++ { p := Point( rand.Float64()*100, rand.Float64()*100, rand.Float64()*100, rand.Float64()*100, ) tx.Set(fmt.Sprintf("p:%d", i), p, nil) } return nil }) var keys, values []string var dists []float64 var pdist float64 var i int db.View(func(tx *Tx) error { tx.Nearby("points", Point(0, 0, 0, 0), func(key, value string, dist float64) bool { if i != 0 && dist < pdist { t.Fatal("out of order") } keys = append(keys, key) values = append(values, value) dists = append(dists, dist) pdist = dist i++ return true }) return nil }) if len(keys) != N { t.Fatalf("expected '%v', got '%v'", N, len(keys)) } } func Example_descKeys() { db, _ := Open(":memory:") db.CreateIndex("name", "*", IndexString) db.Update(func(tx *Tx) error { tx.Set("user:100:first", "Tom", nil) tx.Set("user:100:last", "Johnson", nil) tx.Set("user:101:first", "Janet", nil) tx.Set("user:101:last", "Prichard", nil) tx.Set("user:102:first", "Alan", nil) tx.Set("user:102:last", "Cooper", nil) return nil }) db.View(func(tx *Tx) error { tx.AscendKeys("user:101:*", func(key, value string) bool { fmt.Printf("%s: %s\n", key, value) return true }) tx.AscendKeys("user:10?:*", func(key, value string) bool { fmt.Printf("%s: %s\n", key, value) return true }) tx.AscendKeys("*2*", func(key, value string) bool { fmt.Printf("%s: %s\n", key, value) return true }) tx.DescendKeys("user:101:*", func(key, value string) bool { fmt.Printf("%s: %s\n", key, value) return true }) tx.DescendKeys("*", func(key, value string) bool { fmt.Printf("%s: %s\n", key, value) return true }) return nil }) // Output: // user:101:first: Janet // user:101:last: Prichard // user:100:first: Tom // user:100:last: Johnson // user:101:first: Janet // user:101:last: Prichard // user:102:first: Alan // user:102:last: Cooper // user:102:first: Alan // user:102:last: Cooper // user:101:last: Prichard // user:101:first: Janet // user:102:last: Cooper // user:102:first: Alan // user:101:last: Prichard // user:101:first: Janet // user:100:last: Johnson // user:100:first: Tom } func ExampleDesc() { db, _ := Open(":memory:") db.CreateIndex("last_name_age", "*", IndexJSON("name.last"), Desc(IndexJSON("age"))) db.Update(func(tx *Tx) error { tx.Set("1", `{"name":{"first":"Tom","last":"Johnson"},"age":38}`, nil) tx.Set("2", `{"name":{"first":"Janet","last":"Prichard"},"age":47}`, nil) tx.Set("3", `{"name":{"first":"Carol","last":"Anderson"},"age":52}`, nil) tx.Set("4", `{"name":{"first":"Alan","last":"Cooper"},"age":28}`, nil) tx.Set("5", `{"name":{"first":"Sam","last":"Anderson"},"age":51}`, nil) tx.Set("6", `{"name":{"first":"Melinda","last":"Prichard"},"age":44}`, nil) return nil }) db.View(func(tx *Tx) error { tx.Ascend("last_name_age", func(key, value string) bool { fmt.Printf("%s: %s\n", key, value) return true }) return nil }) // Output: //3: {"name":{"first":"Carol","last":"Anderson"},"age":52} //5: {"name":{"first":"Sam","last":"Anderson"},"age":51} //4: {"name":{"first":"Alan","last":"Cooper"},"age":28} //1: {"name":{"first":"Tom","last":"Johnson"},"age":38} //2: {"name":{"first":"Janet","last":"Prichard"},"age":47} //6: {"name":{"first":"Melinda","last":"Prichard"},"age":44} } func ExampleDB_CreateIndex_jSON() { db, _ := Open(":memory:") db.CreateIndex("last_name", "*", IndexJSON("name.last")) db.CreateIndex("age", "*", IndexJSON("age")) db.Update(func(tx *Tx) error { tx.Set("1", `{"name":{"first":"Tom","last":"Johnson"},"age":38}`, nil) tx.Set("2", `{"name":{"first":"Janet","last":"Prichard"},"age":47}`, nil) tx.Set("3", `{"name":{"first":"Carol","last":"Anderson"},"age":52}`, nil) tx.Set("4", `{"name":{"first":"Alan","last":"Cooper"},"age":28}`, nil) return nil }) db.View(func(tx *Tx) error { fmt.Println("Order by last name") tx.Ascend("last_name", func(key, value string) bool { fmt.Printf("%s: %s\n", key, value) return true }) fmt.Println("Order by age") tx.Ascend("age", func(key, value string) bool { fmt.Printf("%s: %s\n", key, value) return true }) fmt.Println("Order by age range 30-50") tx.AscendRange("age", `{"age":30}`, `{"age":50}`, func(key, value string) bool { fmt.Printf("%s: %s\n", key, value) return true }) return nil }) // Output: // Order by last name // 3: {"name":{"first":"Carol","last":"Anderson"},"age":52} // 4: {"name":{"first":"Alan","last":"Cooper"},"age":28} // 1: {"name":{"first":"Tom","last":"Johnson"},"age":38} // 2: {"name":{"first":"Janet","last":"Prichard"},"age":47} // Order by age // 4: {"name":{"first":"Alan","last":"Cooper"},"age":28} // 1: {"name":{"first":"Tom","last":"Johnson"},"age":38} // 2: {"name":{"first":"Janet","last":"Prichard"},"age":47} // 3: {"name":{"first":"Carol","last":"Anderson"},"age":52} // Order by age range 30-50 // 1: {"name":{"first":"Tom","last":"Johnson"},"age":38} // 2: {"name":{"first":"Janet","last":"Prichard"},"age":47} } func ExampleDB_CreateIndex_strings() { db, _ := Open(":memory:") db.CreateIndex("name", "*", IndexString) db.Update(func(tx *Tx) error { tx.Set("1", "Tom", nil) tx.Set("2", "Janet", nil) tx.Set("3", "Carol", nil) tx.Set("4", "Alan", nil) tx.Set("5", "Sam", nil) tx.Set("6", "Melinda", nil) return nil }) db.View(func(tx *Tx) error { tx.Ascend("name", func(key, value string) bool { fmt.Printf("%s: %s\n", key, value) return true }) return nil }) // Output: //4: Alan //3: Carol //2: Janet //6: Melinda //5: Sam //1: Tom } func ExampleDB_CreateIndex_ints() { db, _ := Open(":memory:") db.CreateIndex("age", "*", IndexInt) db.Update(func(tx *Tx) error { tx.Set("1", "30", nil) tx.Set("2", "51", nil) tx.Set("3", "16", nil) tx.Set("4", "76", nil) tx.Set("5", "23", nil) tx.Set("6", "43", nil) return nil }) db.View(func(tx *Tx) error { tx.Ascend("age", func(key, value string) bool { fmt.Printf("%s: %s\n", key, value) return true }) return nil }) // Output: //3: 16 //5: 23 //1: 30 //6: 43 //2: 51 //4: 76 } func ExampleDB_CreateIndex_multipleFields() { db, _ := Open(":memory:") db.CreateIndex("last_name_age", "*", IndexJSON("name.last"), IndexJSON("age")) db.Update(func(tx *Tx) error { tx.Set("1", `{"name":{"first":"Tom","last":"Johnson"},"age":38}`, nil) tx.Set("2", `{"name":{"first":"Janet","last":"Prichard"},"age":47}`, nil) tx.Set("3", `{"name":{"first":"Carol","last":"Anderson"},"age":52}`, nil) tx.Set("4", `{"name":{"first":"Alan","last":"Cooper"},"age":28}`, nil) tx.Set("5", `{"name":{"first":"Sam","last":"Anderson"},"age":51}`, nil) tx.Set("6", `{"name":{"first":"Melinda","last":"Prichard"},"age":44}`, nil) return nil }) db.View(func(tx *Tx) error { tx.Ascend("last_name_age", func(key, value string) bool { fmt.Printf("%s: %s\n", key, value) return true }) return nil }) // Output: //5: {"name":{"first":"Sam","last":"Anderson"},"age":51} //3: {"name":{"first":"Carol","last":"Anderson"},"age":52} //4: {"name":{"first":"Alan","last":"Cooper"},"age":28} //1: {"name":{"first":"Tom","last":"Johnson"},"age":38} //6: {"name":{"first":"Melinda","last":"Prichard"},"age":44} //2: {"name":{"first":"Janet","last":"Prichard"},"age":47} } func TestNoExpiringItem(t *testing.T) { item := &dbItem{key: "key", val: "val"} if !item.expiresAt().Equal(maxTime) { t.Fatal("item.expiresAt() != maxTime") } if min, max := item.Rect(nil); min != nil || max != nil { t.Fatal("item min,max should both be nil") } } func TestAutoShrink(t *testing.T) { db := testOpen(t) defer testClose(db) for i := 0; i < 1000; i++ { err := db.Update(func(tx *Tx) error { for i := 0; i < 20; i++ { if _, _, err := tx.Set(fmt.Sprintf("HELLO:%d", i), "WORLD", nil); err != nil { return err } } return nil }) if err != nil { t.Fatal(err) } } db = testReOpen(t, db) defer testClose(db) db.config.AutoShrinkMinSize = 64 * 1024 // 64K for i := 0; i < 2000; i++ { err := db.Update(func(tx *Tx) error { for i := 0; i < 20; i++ { if _, _, err := tx.Set(fmt.Sprintf("HELLO:%d", i), "WORLD", nil); err != nil { return err } } return nil }) if err != nil { t.Fatal(err) } } time.Sleep(time.Second * 3) db = testReOpen(t, db) defer testClose(db) err := db.View(func(tx *Tx) error { n, err := tx.Len() if err != nil { return err } if n != 20 { t.Fatalf("expecting 20, got %v", n) } return nil }) if err != nil { t.Fatal(err) } } // test database format loading func TestDatabaseFormat(t *testing.T) { // should succeed func() { resp := strings.Join([]string{ "*3\r\n$3\r\nset\r\n$4\r\nvar1\r\n$4\r\n1234\r\n", "*3\r\n$3\r\nset\r\n$4\r\nvar2\r\n$4\r\n1234\r\n", "*2\r\n$3\r\ndel\r\n$4\r\nvar1\r\n", "*5\r\n$3\r\nset\r\n$3\r\nvar\r\n$3\r\nval\r\n$2\r\nex\r\n$2\r\n10\r\n", }, "") if err := os.RemoveAll("data.db"); err != nil { t.Fatal(err) } if err := ioutil.WriteFile("data.db", []byte(resp), 0666); err != nil { t.Fatal(err) } db := testOpen(t) defer testClose(db) }() testFormat := func(t *testing.T, expectValid bool, resp string, do func(db *DB) error) { t.Helper() os.RemoveAll("data.db") if err := ioutil.WriteFile("data.db", []byte(resp), 0666); err != nil { t.Fatal(err) } defer os.RemoveAll("data.db") db, err := Open("data.db") if err == nil { if do != nil { if err := do(db); err != nil { t.Fatal(err) } } if err := db.Close(); err != nil { t.Fatal(err) } } if err == nil && !expectValid { t.Fatalf("expected invalid database") } else if err != nil && expectValid { t.Fatalf("expected valid database, got '%s'", err) } } // basic valid commands testFormat(t, true, "*2\r\n$3\r\nDEL\r\n$5\r\nHELLO\r\n", nil) testFormat(t, true, "*3\r\n$3\r\nSET\r\n$5\r\nHELLO\r\n$5\r\nWORLD\r\n", nil) testFormat(t, true, "*1\r\n$7\r\nFLUSHDB\r\n", nil) // commands with invalid names or arguments testFormat(t, false, "*3\r\n$3\r\nDEL\r\n$5\r\nHELLO\r\n$5\r\nWORLD\r\n", nil) testFormat(t, false, "*2\r\n$3\r\nSET\r\n$5\r\nHELLO\r\n", nil) testFormat(t, false, "*1\r\n$6\r\nSET123\r\n", nil) // partial tail commands should be ignored but allowed pcmd := "*3\r\n$3\r\nSET\r\n$5\r\nHELLO\r\n$5\r\nWORLD\r\n" for i := 1; i < len(pcmd); i++ { cmd := "*3\r\n$3\r\nSET\r\n$5\r\nHELLO\r\n$5\r\nJELLO\r\n" testFormat(t, true, cmd+pcmd[:len(pcmd)-i], func(db *DB) error { return db.View(func(tx *Tx) error { val, err := tx.Get("HELLO") if err != nil { return err } if val != "JELLO" { return fmt.Errorf("expected '%s', got '%s'", "JELLO", val) } return nil }) }) } // commands with invalid formatting testFormat(t, false, "^3\r\n$3\r\nSET\r\n$5\r\nHELLO\r\n$5\r\nWORLD\r\n", nil) testFormat(t, false, "*3\n$3\r\nSET\r\n$5\r\nHELLO\r\n$5\r\nWORLD\r\n", nil) testFormat(t, false, "*\n$3\r\nSET\r\n$5\r\nHELLO\r\n$5\r\nWORLD\r\n", nil) testFormat(t, false, "*3\r\n^3\r\nSET\r\n$5\r\nHELLO\r\n$5\r\nWORLD\r\n", nil) testFormat(t, false, "*3\r\n$\r\nSET\r\n$5\r\nHELLO\r\n$5\r\nWORLD\r\n", nil) testFormat(t, false, "*3\r\n$3\nSET\r\n$5\r\nHELLO\r\n$5\r\nWORLD\r\n", nil) testFormat(t, false, "*3\r\n$3SET\r\n$5\r\nHELLO\r\n$5\r\nWORLD\r\n", nil) testFormat(t, false, "*3\r\n$3\r\nSET\r\n$5\r\nHELLO\r\n$5\r\nWORLD\r\n123\n", nil) // commands with nuls testFormat(t, true, "\u0000*3\r\n$3\r\nSET\r\n$5\r\nHELLO\r\n$5\r\nWORLD\r\n"+ "\u0000\u0000*3\r\n$3\r\nSET\r\n$5\r\nHELLO\r\n$5\r\nJELLO\r\n\u0000", func(db *DB) error { return db.View(func(tx *Tx) error { val, err := tx.Get("HELLO") if err != nil { return err } if val != "JELLO" { return fmt.Errorf("expected '%s', got '%s'", "JELLO", val) } return nil }) }) } func TestInsertsAndDeleted(t *testing.T) { db := testOpen(t) defer testClose(db) if err := db.CreateIndex("any", "*", IndexString); err != nil { t.Fatal(err) } if err := db.CreateSpatialIndex("rect", "*", IndexRect); err != nil { t.Fatal(err) } if err := db.Update(func(tx *Tx) error { if _, _, err := tx.Set("item1", "value1", &SetOptions{Expires: true, TTL: time.Second}); err != nil { return err } if _, _, err := tx.Set("item2", "value2", nil); err != nil { return err } if _, _, err := tx.Set("item3", "value3", &SetOptions{Expires: true, TTL: time.Second}); err != nil { return err } return nil }); err != nil { t.Fatal(err) } // test replacing items in the database if err := db.Update(func(tx *Tx) error { if _, _, err := tx.Set("item1", "nvalue1", nil); err != nil { return err } if _, _, err := tx.Set("item2", "nvalue2", nil); err != nil { return err } if _, err := tx.Delete("item3"); err != nil { return err } return nil }); err != nil { t.Fatal(err) } } func TestInsertDoesNotMisuseIndex(t *testing.T) { db := testOpen(t) defer testClose(db) fail := func(a, b string) bool { t.Fatal("Misused index"); return false } if err := db.CreateIndex("some", "a*", fail); err != nil { // Only one item is eligible for the index, so no comparison is necessary. t.Fatal(err) } if err := db.Update(func(tx *Tx) error { if _, _, err := tx.Set("a", "1", nil); err != nil { return err } if _, _, err := tx.Set("b", "1", nil); err != nil { return err } return nil }); err != nil { t.Fatal(err) } if err := db.Update(func(tx *Tx) error { _, _, err := tx.Set("b", "2", nil) return err }); err != nil { t.Fatal(err) } } func TestDeleteDoesNotMisuseIndex(t *testing.T) { db := testOpen(t) defer testClose(db) fail := func(a, b string) bool { t.Fatal("Misused index"); return false } if err := db.CreateIndex("some", "a*", fail); err != nil { // Only one item is eligible for the index, so no comparison is necessary. t.Fatal(err) } if err := db.Update(func(tx *Tx) error { if _, _, err := tx.Set("a", "1", nil); err != nil { return err } if _, _, err := tx.Set("b", "1", nil); err != nil { return err } return nil }); err != nil { t.Fatal(err) } if err := db.Update(func(tx *Tx) error { _, err := tx.Delete("b") return err }); err != nil { t.Fatal(err) } } // test index compare functions func TestIndexCompare(t *testing.T) { if !IndexFloat("1.5", "1.6") { t.Fatalf("expected true, got false") } if !IndexInt("-1", "2") { t.Fatalf("expected true, got false") } if !IndexUint("10", "25") { t.Fatalf("expected true, got false") } if !IndexBinary("Hello", "hello") { t.Fatalf("expected true, got false") } if IndexString("hello", "hello") { t.Fatalf("expected false, got true") } if IndexString("Hello", "hello") { t.Fatalf("expected false, got true") } if IndexString("hello", "Hello") { t.Fatalf("expected false, got true") } if !IndexString("gello", "Hello") { t.Fatalf("expected true, got false") } if IndexString("Hello", "gello") { t.Fatalf("expected false, got true") } if Rect(IndexRect("[1 2 3 4],[5 6 7 8]")) != "[1 2 3 4],[5 6 7 8]" { t.Fatalf("expected '%v', got '%v'", "[1 2 3 4],[5 6 7 8]", Rect(IndexRect("[1 2 3 4],[5 6 7 8]"))) } if Rect(IndexRect("[1 2 3 4]")) != "[1 2 3 4]" { t.Fatalf("expected '%v', got '%v'", "[1 2 3 4]", Rect(IndexRect("[1 2 3 4]"))) } if Rect(nil, nil) != "[]" { t.Fatalf("expected '%v', got '%v'", "", Rect(nil, nil)) } if Point(1, 2, 3) != "[1 2 3]" { t.Fatalf("expected '%v', got '%v'", "[1 2 3]", Point(1, 2, 3)) } } // test opening a folder. func TestOpeningAFolder(t *testing.T) { if err := os.RemoveAll("dir.tmp"); err != nil { t.Fatal(err) } if err := os.Mkdir("dir.tmp", 0700); err != nil { t.Fatal(err) } defer func() { _ = os.RemoveAll("dir.tmp") }() db, err := Open("dir.tmp") if err == nil { if err := db.Close(); err != nil { t.Fatal(err) } t.Fatalf("opening a directory should not be allowed") } } // test opening an invalid resp file. func TestOpeningInvalidDatabaseFile(t *testing.T) { if err := os.RemoveAll("data.db"); err != nil { t.Fatal(err) } if err := ioutil.WriteFile("data.db", []byte("invalid\r\nfile"), 0666); err != nil { t.Fatal(err) } defer func() { _ = os.RemoveAll("data.db") }() db, err := Open("data.db") if err == nil { if err := db.Close(); err != nil { t.Fatal(err) } t.Fatalf("invalid database should not be allowed") } } // test closing a closed database. func TestOpeningClosedDatabase(t *testing.T) { if err := os.RemoveAll("data.db"); err != nil { t.Fatal(err) } db, err := Open("data.db") if err != nil { t.Fatal(err) } defer func() { _ = os.RemoveAll("data.db") }() if err := db.Close(); err != nil { t.Fatal(err) } if err := db.Close(); err != ErrDatabaseClosed { t.Fatal("should not be able to close a closed database") } db, err = Open(":memory:") if err != nil { t.Fatal(err) } if err := db.Close(); err != nil { t.Fatal(err) } if err := db.Close(); err != ErrDatabaseClosed { t.Fatal("should not be able to close a closed database") } } // test shrinking a database. func TestShrink(t *testing.T) { db := testOpen(t) defer testClose(db) if err := db.Shrink(); err != nil { t.Fatal(err) } fi, err := os.Stat("data.db") if err != nil { t.Fatal(err) } if fi.Size() != 0 { t.Fatalf("expected %v, got %v", 0, fi.Size()) } // add 10 items err = db.Update(func(tx *Tx) error { for i := 0; i < 10; i++ { if _, _, err := tx.Set(fmt.Sprintf("key%d", i), fmt.Sprintf("val%d", i), nil); err != nil { return err } } return nil }) if err != nil { t.Fatal(err) } // add the same 10 items // this will create 10 duplicate log entries err = db.Update(func(tx *Tx) error { for i := 0; i < 10; i++ { if _, _, err := tx.Set(fmt.Sprintf("key%d", i), fmt.Sprintf("val%d", i), nil); err != nil { return err } } return nil }) if err != nil { t.Fatal(err) } fi, err = os.Stat("data.db") if err != nil { t.Fatal(err) } sz1 := fi.Size() if sz1 == 0 { t.Fatalf("expected > 0, got %v", sz1) } if err := db.Shrink(); err != nil { t.Fatal(err) } fi, err = os.Stat("data.db") if err != nil { t.Fatal(err) } sz2 := fi.Size() if sz2 >= sz1 { t.Fatalf("expected < %v, got %v", sz1, sz2) } if err := db.Close(); err != nil { t.Fatal(err) } if err := db.Shrink(); err != ErrDatabaseClosed { t.Fatal("shrink on a closed databse should not be allowed") } // Now we will open a db that does not persist db, err = Open(":memory:") if err != nil { t.Fatal(err) } defer func() { _ = db.Close() }() // add 10 items err = db.Update(func(tx *Tx) error { for i := 0; i < 10; i++ { if _, _, err := tx.Set(fmt.Sprintf("key%d", i), fmt.Sprintf("val%d", i), nil); err != nil { return err } } return nil }) if err != nil { t.Fatal(err) } // add the same 10 items // this will create 10 duplicate log entries err = db.Update(func(tx *Tx) error { for i := 0; i < 10; i++ { if _, _, err := tx.Set(fmt.Sprintf("key%d", i), fmt.Sprintf("val%d", i), nil); err != nil { return err } } return nil }) if err != nil { t.Fatal(err) } err = db.View(func(tx *Tx) error { n, err := tx.Len() if err != nil { t.Fatal(err) } if n != 10 { t.Fatalf("expecting %v, got %v", 10, n) } return nil }) if err != nil { t.Fatal(err) } // this should succeed even though it's basically a noop. if err := db.Shrink(); err != nil { t.Fatal(err) } } func TestVariousIndexOperations(t *testing.T) { db := testOpen(t) defer testClose(db) // test creating an index with no index name. err := db.CreateIndex("", "", nil) if err == nil { t.Fatal("should not be able to create an index with no name") } // test creating an index with a name that has already been used. err = db.CreateIndex("hello", "", nil) if err != nil { t.Fatal(err) } err = db.CreateIndex("hello", "", nil) if err == nil { t.Fatal("should not be able to create a duplicate index") } err = db.Update(func(tx *Tx) error { if _, _, err := tx.Set("user:1", "tom", nil); err != nil { return err } if _, _, err := tx.Set("user:2", "janet", nil); err != nil { return err } if _, _, err := tx.Set("alt:1", "from", nil); err != nil { return err } if _, _, err := tx.Set("alt:2", "there", nil); err != nil { return err } if _, _, err := tx.Set("rect:1", "[1 2],[3 4]", nil); err != nil { return err } if _, _, err := tx.Set("rect:2", "[5 6],[7 8]", nil); err != nil { return err } return nil }) if err != nil { t.Fatal(err) } // test creating an index after adding items. use pattern matching. have some items in the match and some not. if err := db.CreateIndex("string", "user:*", IndexString); err != nil { t.Fatal(err) } // test creating a spatial index after adding items. use pattern matching. have some items in the match and some not. if err := db.CreateSpatialIndex("rect", "rect:*", IndexRect); err != nil { t.Fatal(err) } // test dropping an index if err := db.DropIndex("hello"); err != nil { t.Fatal(err) } // test dropping an index with no name if err := db.DropIndex(""); err == nil { t.Fatal("should not be allowed to drop an index with no name") } // test dropping an index with no name if err := db.DropIndex("na"); err == nil { t.Fatal("should not be allowed to drop an index that does not exist") } // test retrieving index names names, err := db.Indexes() if err != nil { t.Fatal(err) } if strings.Join(names, ",") != "rect,string" { t.Fatalf("expecting '%v', got '%v'", "rect,string", strings.Join(names, ",")) } // test creating an index after closing database if err := db.Close(); err != nil { t.Fatal(err) } if err := db.CreateIndex("new-index", "", nil); err != ErrDatabaseClosed { t.Fatal("should not be able to create an index on a closed database") } // test getting index names after closing database if _, err := db.Indexes(); err != ErrDatabaseClosed { t.Fatal("should not be able to get index names on a closed database") } // test dropping an index after closing database if err := db.DropIndex("rect"); err != ErrDatabaseClosed { t.Fatal("should not be able to drop an index on a closed database") } } func test(t *testing.T, a, b bool) { if a != b { t.Fatal("failed, bummer...") } } func TestBasic(t *testing.T) { rand.Seed(time.Now().UnixNano()) db := testOpen(t) defer testClose(db) // create a simple index if err := db.CreateIndex("users", "fun:user:*", IndexString); err != nil { t.Fatal(err) } // create a spatial index if err := db.CreateSpatialIndex("rects", "rect:*", IndexRect); err != nil { t.Fatal(err) } if true { err := db.Update(func(tx *Tx) error { if _, _, err := tx.Set("fun:user:0", "tom", nil); err != nil { return err } if _, _, err := tx.Set("fun:user:1", "Randi", nil); err != nil { return err } if _, _, err := tx.Set("fun:user:2", "jane", nil); err != nil { return err } if _, _, err := tx.Set("fun:user:4", "Janet", nil); err != nil { return err } if _, _, err := tx.Set("fun:user:5", "Paula", nil); err != nil { return err } if _, _, err := tx.Set("fun:user:6", "peter", nil); err != nil { return err } if _, _, err := tx.Set("fun:user:7", "Terri", nil); err != nil { return err } return nil }) if err != nil { t.Fatal(err) } // add some random items start := time.Now() if err := db.Update(func(tx *Tx) error { for _, i := range rand.Perm(100) { if _, _, err := tx.Set(fmt.Sprintf("tag:%d", i+100), fmt.Sprintf("val:%d", rand.Int()%100+100), nil); err != nil { return err } } return nil }); err != nil { t.Fatal(err) } if false { println(time.Since(start).String(), db.keys.Len()) } // add some random rects if err := db.Update(func(tx *Tx) error { if _, _, err := tx.Set("rect:1", Rect([]float64{10, 10}, []float64{20, 20}), nil); err != nil { return err } if _, _, err := tx.Set("rect:2", Rect([]float64{15, 15}, []float64{24, 24}), nil); err != nil { return err } if _, _, err := tx.Set("rect:3", Rect([]float64{17, 17}, []float64{27, 27}), nil); err != nil { return err } return nil }); err != nil { t.Fatal(err) } } // verify the data has been created buf := &bytes.Buffer{} err := db.View(func(tx *Tx) error { err := tx.Ascend("users", func(key, val string) bool { fmt.Fprintf(buf, "%s %s\n", key, val) return true }) if err != nil { t.Fatal(err) } err = tx.AscendRange("", "tag:170", "tag:172", func(key, val string) bool { fmt.Fprintf(buf, "%s\n", key) return true }) if err != nil { t.Fatal(err) } err = tx.AscendGreaterOrEqual("", "tag:195", func(key, val string) bool { fmt.Fprintf(buf, "%s\n", key) return true }) if err != nil { t.Fatal(err) } err = tx.AscendGreaterOrEqual("", "rect:", func(key, val string) bool { if !strings.HasPrefix(key, "rect:") { return false } min, max := IndexRect(val) fmt.Fprintf(buf, "%s: %v,%v\n", key, min, max) return true }) if err != nil { t.Fatal(err) } expect := make([]string, 2) n := 0 err = tx.Intersects("rects", "[0 0],[15 15]", func(key, val string) bool { if n == 2 { t.Fatalf("too many rects where received, expecting only two") } min, max := IndexRect(val) s := fmt.Sprintf("%s: %v,%v\n", key, min, max) if key == "rect:1" { expect[0] = s } else if key == "rect:2" { expect[1] = s } n++ return true }) if err != nil { t.Fatal(err) } for _, s := range expect { if _, err := buf.WriteString(s); err != nil { return err } } return nil }) if err != nil { t.Fatal(err) } res := ` fun:user:2 jane fun:user:4 Janet fun:user:5 Paula fun:user:6 peter fun:user:1 Randi fun:user:7 Terri fun:user:0 tom tag:170 tag:171 tag:195 tag:196 tag:197 tag:198 tag:199 rect:1: [10 10],[20 20] rect:2: [15 15],[24 24] rect:3: [17 17],[27 27] rect:1: [10 10],[20 20] rect:2: [15 15],[24 24] ` res = strings.Replace(res, "\r", "", -1) if strings.TrimSpace(buf.String()) != strings.TrimSpace(res) { t.Fatalf("expected [%v], got [%v]", strings.TrimSpace(res), strings.TrimSpace(buf.String())) } } func TestIndexAscend(t *testing.T) { rand.Seed(time.Now().UnixNano()) db := testOpen(t) defer testClose(db) // create a simple index if err := db.CreateIndex("usr", "usr:*", IndexInt); err != nil { t.Fatal(err) } if err := db.Update(func(tx *Tx) error { for i := 10; i > 0; i-- { tx.Set(fmt.Sprintf("usr:%d", i), fmt.Sprintf("%d", 10-i), nil) } return nil }); err != nil { t.Fatal(err) } buf := &bytes.Buffer{} err := db.View(func(tx *Tx) error { tx.Ascend("usr", func(key, value string) bool { fmt.Fprintf(buf, "%s %s\n", key, value) return true }) fmt.Fprintln(buf) tx.AscendGreaterOrEqual("usr", "8", func(key, value string) bool { fmt.Fprintf(buf, "%s %s\n", key, value) return true }) fmt.Fprintln(buf) tx.AscendLessThan("usr", "3", func(key, value string) bool { fmt.Fprintf(buf, "%s %s\n", key, value) return true }) fmt.Fprintln(buf) tx.AscendRange("usr", "4", "8", func(key, value string) bool { fmt.Fprintf(buf, "%s %s\n", key, value) return true }) return nil }) if err != nil { t.Fatal(err) } res := ` usr:10 0 usr:9 1 usr:8 2 usr:7 3 usr:6 4 usr:5 5 usr:4 6 usr:3 7 usr:2 8 usr:1 9 usr:2 8 usr:1 9 usr:10 0 usr:9 1 usr:8 2 usr:6 4 usr:5 5 usr:4 6 usr:3 7 ` res = strings.Replace(res, "\r", "", -1) s1 := strings.TrimSpace(buf.String()) s2 := strings.TrimSpace(res) if s1 != s2 { t.Fatalf("expected [%v], got [%v]", s1, s2) } } func testRectStringer(min, max []float64) error { nmin, nmax := IndexRect(Rect(min, max)) if len(nmin) != len(min) { return fmt.Errorf("rect=%v,%v, expect=%v,%v", nmin, nmax, min, max) } for i := 0; i < len(min); i++ { if min[i] != nmin[i] || max[i] != nmax[i] { return fmt.Errorf("rect=%v,%v, expect=%v,%v", nmin, nmax, min, max) } } return nil } func TestRectStrings(t *testing.T) { test(t, Rect(IndexRect(Point(1))) == "[1]", true) test(t, Rect(IndexRect(Point(1, 2, 3, 4))) == "[1 2 3 4]", true) test(t, Rect(IndexRect(Rect(IndexRect("[1 2],[1 2]")))) == "[1 2]", true) test(t, Rect(IndexRect(Rect(IndexRect("[1 2],[2 2]")))) == "[1 2],[2 2]", true) test(t, Rect(IndexRect(Rect(IndexRect("[1 2],[2 2],[3]")))) == "[1 2],[2 2]", true) test(t, Rect(IndexRect(Rect(IndexRect("[1 2]")))) == "[1 2]", true) test(t, Rect(IndexRect(Rect(IndexRect("[1.5 2 4.5 5.6]")))) == "[1.5 2 4.5 5.6]", true) test(t, Rect(IndexRect(Rect(IndexRect("[1.5 2 4.5 5.6 -1],[]")))) == "[1.5 2 4.5 5.6 -1]", true) test(t, Rect(IndexRect(Rect(IndexRect("[]")))) == "[]", true) test(t, Rect(IndexRect(Rect(IndexRect("")))) == "[]", true) if err := testRectStringer(nil, nil); err != nil { t.Fatal(err) } if err := testRectStringer([]float64{}, []float64{}); err != nil { t.Fatal(err) } if err := testRectStringer([]float64{1}, []float64{2}); err != nil { t.Fatal(err) } if err := testRectStringer([]float64{1, 2}, []float64{3, 4}); err != nil { t.Fatal(err) } if err := testRectStringer([]float64{1, 2, 3}, []float64{4, 5, 6}); err != nil { t.Fatal(err) } if err := testRectStringer([]float64{1, 2, 3, 4}, []float64{5, 6, 7, 8}); err != nil { t.Fatal(err) } if err := testRectStringer([]float64{1, 2, 3, 4, 5}, []float64{6, 7, 8, 9, 10}); err != nil { t.Fatal(err) } } // TestTTLReOpen test setting a TTL and then immediately closing the database and // then waiting the TTL before reopening. The key should not be accessible. func TestTTLReOpen(t *testing.T) { ttl := time.Second * 3 db := testOpen(t) defer testClose(db) err := db.Update(func(tx *Tx) error { if _, _, err := tx.Set("key1", "val1", &SetOptions{Expires: true, TTL: ttl}); err != nil { return err } return nil }) if err != nil { t.Fatal(err) } db = testReOpenDelay(t, db, ttl/4) err = db.View(func(tx *Tx) error { val, err := tx.Get("key1") if err != nil { return err } if val != "val1" { t.Fatalf("expecting '%v', got '%v'", "val1", val) } return nil }) if err != nil { t.Fatal(err) } db = testReOpenDelay(t, db, ttl-ttl/4) defer testClose(db) err = db.View(func(tx *Tx) error { val, err := tx.Get("key1") if err == nil || err != ErrNotFound || val != "" { t.Fatal("expecting not found") } return nil }) if err != nil { t.Fatal(err) } } func TestTTL(t *testing.T) { db := testOpen(t) defer testClose(db) err := db.Update(func(tx *Tx) error { if _, _, err := tx.Set("key1", "val1", &SetOptions{Expires: true, TTL: time.Second}); err != nil { return err } if _, _, err := tx.Set("key2", "val2", nil); err != nil { return err } return nil }) if err != nil { t.Fatal(err) } err = db.View(func(tx *Tx) error { dur1, err := tx.TTL("key1") if err != nil { t.Fatal(err) } if dur1 > time.Second || dur1 <= 0 { t.Fatalf("expecting between zero and one second, got '%v'", dur1) } dur1, err = tx.TTL("key2") if err != nil { t.Fatal(err) } if dur1 >= 0 { t.Fatalf("expecting a negative value, got '%v'", dur1) } return nil }) if err != nil { t.Fatal(err) } } func TestConfig(t *testing.T) { db := testOpen(t) defer testClose(db) err := db.SetConfig(Config{SyncPolicy: SyncPolicy(-1)}) if err == nil { t.Fatal("expecting a config syncpolicy error") } err = db.SetConfig(Config{SyncPolicy: SyncPolicy(3)}) if err == nil { t.Fatal("expecting a config syncpolicy error") } err = db.SetConfig(Config{SyncPolicy: Never}) if err != nil { t.Fatal(err) } err = db.SetConfig(Config{SyncPolicy: EverySecond}) if err != nil { t.Fatal(err) } err = db.SetConfig(Config{AutoShrinkMinSize: 100, AutoShrinkPercentage: 200, SyncPolicy: Always}) if err != nil { t.Fatal(err) } var c Config if err := db.ReadConfig(&c); err != nil { t.Fatal(err) } if c.AutoShrinkMinSize != 100 || c.AutoShrinkPercentage != 200 && c.SyncPolicy != Always { t.Fatalf("expecting %v, %v, and %v, got %v, %v, and %v", 100, 200, Always, c.AutoShrinkMinSize, c.AutoShrinkPercentage, c.SyncPolicy) } } func testUint64Hex(n uint64) string { s := strconv.FormatUint(n, 16) s = "0000000000000000" + s return s[len(s)-16:] } func textHexUint64(s string) uint64 { n, _ := strconv.ParseUint(s, 16, 64) return n } func benchClose(t *testing.B, persist bool, db *DB) { if persist { if err := os.RemoveAll("data.db"); err != nil { t.Fatal(err) } } if err := db.Close(); err != nil { t.Fatal(err) } } func benchOpenFillData(t *testing.B, N int, set, persist, random bool, geo bool, batch int) (db *DB, keys, vals []string) { /// t.StopTimer() rand.Seed(time.Now().UnixNano()) var err error if persist { if err := os.RemoveAll("data.db"); err != nil { t.Fatal(err) } db, err = Open("data.db") } else { db, err = Open(":memory:") } if err != nil { t.Fatal(err) } keys = make([]string, N) vals = make([]string, N) perm := rand.Perm(N) for i := 0; i < N; i++ { if random && set { keys[perm[i]] = testUint64Hex(uint64(i)) vals[perm[i]] = strconv.FormatInt(rand.Int63()%1000+1000, 10) } else { keys[i] = testUint64Hex(uint64(i)) vals[i] = strconv.FormatInt(rand.Int63()%1000+1000, 10) } } if set { t.StartTimer() } for i := 0; i < N; { err := db.Update(func(tx *Tx) error { var err error for j := 0; j < batch && i < N; j++ { _, _, err = tx.Set(keys[i], vals[i], nil) i++ } return err }) if err != nil { t.Fatal(err) } } if set { t.StopTimer() } var n uint64 err = db.View(func(tx *Tx) error { err := tx.Ascend("", func(key, value string) bool { n2 := textHexUint64(key) if n2 != n { t.Fatalf("expecting '%v', got '%v'", n2, n) } n++ return true }) return err }) if err != nil { t.Fatal(err) } if n != uint64(N) { t.Fatalf("expecting '%v', got '%v'", N, n) } t.StartTimer() return db, keys, vals } func benchSetGet(t *testing.B, set, persist, random bool, batch int) { N := t.N for N > 0 { n := 0 if N >= 100000 { n = 100000 } else { n = N } func() { db, keys, _ := benchOpenFillData(t, n, set, persist, random, false, batch) defer benchClose(t, persist, db) if !set { for i := 0; i < n; { err := db.View(func(tx *Tx) error { var err error for j := 0; j < batch && i < n; j++ { _, err = tx.Get(keys[i]) i++ } return err }) if err != nil { t.Fatal(err) } } } }() N -= n } } // Set Persist func Benchmark_Set_Persist_Random_1(t *testing.B) { benchSetGet(t, true, true, true, 1) } func Benchmark_Set_Persist_Random_10(t *testing.B) { benchSetGet(t, true, true, true, 10) } func Benchmark_Set_Persist_Random_100(t *testing.B) { benchSetGet(t, true, true, true, 100) } func Benchmark_Set_Persist_Sequential_1(t *testing.B) { benchSetGet(t, true, true, false, 1) } func Benchmark_Set_Persist_Sequential_10(t *testing.B) { benchSetGet(t, true, true, false, 10) } func Benchmark_Set_Persist_Sequential_100(t *testing.B) { benchSetGet(t, true, true, false, 100) } // Set NoPersist func Benchmark_Set_NoPersist_Random_1(t *testing.B) { benchSetGet(t, true, false, true, 1) } func Benchmark_Set_NoPersist_Random_10(t *testing.B) { benchSetGet(t, true, false, true, 10) } func Benchmark_Set_NoPersist_Random_100(t *testing.B) { benchSetGet(t, true, false, true, 100) } func Benchmark_Set_NoPersist_Sequential_1(t *testing.B) { benchSetGet(t, true, false, false, 1) } func Benchmark_Set_NoPersist_Sequential_10(t *testing.B) { benchSetGet(t, true, false, false, 10) } func Benchmark_Set_NoPersist_Sequential_100(t *testing.B) { benchSetGet(t, true, false, false, 100) } // Get func Benchmark_Get_1(t *testing.B) { benchSetGet(t, false, false, false, 1) } func Benchmark_Get_10(t *testing.B) { benchSetGet(t, false, false, false, 10) } func Benchmark_Get_100(t *testing.B) { benchSetGet(t, false, false, false, 100) } func benchScan(t *testing.B, asc bool, count int) { N := count db, _, _ := benchOpenFillData(t, N, false, false, false, false, 100) defer benchClose(t, false, db) for i := 0; i < t.N; i++ { count := 0 err := db.View(func(tx *Tx) error { if asc { return tx.Ascend("", func(key, val string) bool { count++ return true }) } return tx.Descend("", func(key, val string) bool { count++ return true }) }) if err != nil { t.Fatal(err) } if count != N { t.Fatalf("expecting '%v', got '%v'", N, count) } } } func Benchmark_Ascend_1(t *testing.B) { benchScan(t, true, 1) } func Benchmark_Ascend_10(t *testing.B) { benchScan(t, true, 10) } func Benchmark_Ascend_100(t *testing.B) { benchScan(t, true, 100) } func Benchmark_Ascend_1000(t *testing.B) { benchScan(t, true, 1000) } func Benchmark_Ascend_10000(t *testing.B) { benchScan(t, true, 10000) } func Benchmark_Descend_1(t *testing.B) { benchScan(t, false, 1) } func Benchmark_Descend_10(t *testing.B) { benchScan(t, false, 10) } func Benchmark_Descend_100(t *testing.B) { benchScan(t, false, 100) } func Benchmark_Descend_1000(t *testing.B) { benchScan(t, false, 1000) } func Benchmark_Descend_10000(t *testing.B) { benchScan(t, false, 10000) } /* func Benchmark_Spatial_2D(t *testing.B) { N := 100000 db, _, _ := benchOpenFillData(t, N, true, true, false, true, 100) defer benchClose(t, false, db) } */ func TestCoverCloseAlreadyClosed(t *testing.T) { db := testOpen(t) defer testClose(db) _ = db.file.Close() if err := db.Close(); err == nil { t.Fatal("expecting an error") } } func TestCoverConfigClosed(t *testing.T) { db := testOpen(t) defer testClose(db) _ = db.Close() var config Config if err := db.ReadConfig(&config); err != ErrDatabaseClosed { t.Fatal("expecting database closed error") } if err := db.SetConfig(config); err != ErrDatabaseClosed { t.Fatal("expecting database closed error") } } func TestCoverShrinkShrink(t *testing.T) { db := testOpen(t) defer testClose(db) if err := db.Update(func(tx *Tx) error { for i := 0; i < 10000; i++ { _, _, err := tx.Set(fmt.Sprintf("%d", i), fmt.Sprintf("%d", i), nil) if err != nil { return err } } return nil }); err != nil { t.Fatal(err) } if err := db.Update(func(tx *Tx) error { for i := 250; i < 250+100; i++ { _, err := tx.Delete(fmt.Sprintf("%d", i)) if err != nil { return err } } return nil }); err != nil { t.Fatal(err) } var err1, err2 error var wg sync.WaitGroup wg.Add(2) go func() { defer wg.Done() err1 = db.Shrink() }() go func() { defer wg.Done() err2 = db.Shrink() }() wg.Wait() //println(123) //fmt.Printf("%v\n%v\n", err1, err2) if err1 != ErrShrinkInProcess && err2 != ErrShrinkInProcess { t.Fatal("expecting a shrink in process error") } db = testReOpen(t, db) defer testClose(db) if err := db.View(func(tx *Tx) error { n, err := tx.Len() if err != nil { return err } if n != 9900 { t.Fatal("expecting 9900 items") } return nil }); err != nil { t.Fatal(err) } } func TestPreviousItem(t *testing.T) { db := testOpen(t) defer testClose(db) err := db.Update(func(tx *Tx) error { _, _, err := tx.Set("hello", "world", nil) if err != nil { return err } prev, replaced, err := tx.Set("hello", "planet", nil) if err != nil { return err } if !replaced { t.Fatal("should be replaced") } if prev != "world" { t.Fatalf("expecting '%v', got '%v'", "world", prev) } return nil }) if err != nil { t.Fatal(err) } } func TestJSONIndex(t *testing.T) { db := testOpen(t) defer testClose(db) _ = db.CreateIndex("last_name", "*", IndexJSON("name.last")) _ = db.CreateIndex("last_name_cs", "*", IndexJSONCaseSensitive("name.last")) _ = db.CreateIndex("age", "*", IndexJSON("age")) _ = db.CreateIndex("student", "*", IndexJSON("student")) _ = db.Update(func(tx *Tx) error { _, _, _ = tx.Set("1", `{"name":{"first":"Tom","last":"Johnson"},"age":38,"student":false}`, nil) _, _, _ = tx.Set("2", `{"name":{"first":"Janet","last":"Prichard"},"age":47,"student":true}`, nil) _, _, _ = tx.Set("3", `{"name":{"first":"Carol","last":"Anderson"},"age":52,"student":true}`, nil) _, _, _ = tx.Set("4", `{"name":{"first":"Alan","last":"Cooper"},"age":28,"student":false}`, nil) _, _, _ = tx.Set("5", `{"name":{"first":"bill","last":"frank"},"age":21,"student":true}`, nil) _, _, _ = tx.Set("6", `{"name":{"first":"sally","last":"randall"},"age":68,"student":false}`, nil) return nil }) var keys []string _ = db.View(func(tx *Tx) error { _ = tx.Ascend("last_name_cs", func(key, value string) bool { //fmt.Printf("%s: %s\n", key, value) keys = append(keys, key) return true }) _ = tx.Ascend("last_name", func(key, value string) bool { //fmt.Printf("%s: %s\n", key, value) keys = append(keys, key) return true }) _ = tx.Ascend("age", func(key, value string) bool { //fmt.Printf("%s: %s\n", key, value) keys = append(keys, key) return true }) _ = tx.Ascend("student", func(key, value string) bool { //fmt.Printf("%s: %s\n", key, value) keys = append(keys, key) return true }) return nil }) expect := "3,4,1,2,5,6,3,4,5,1,2,6,5,4,1,2,3,6,1,4,6,2,3,5" if strings.Join(keys, ",") != expect { t.Fatalf("expected %v, got %v", expect, strings.Join(keys, ",")) } } func TestOnExpiredSync(t *testing.T) { db := testOpen(t) defer testClose(db) var config Config if err := db.ReadConfig(&config); err != nil { t.Fatal(err) } hits := make(chan int, 3) config.OnExpiredSync = func(key, value string, tx *Tx) error { n, err := strconv.Atoi(value) if err != nil { return err } defer func() { hits <- n }() if n >= 2 { _, err = tx.Delete(key) if err != ErrNotFound { return err } return nil } n++ _, _, err = tx.Set(key, strconv.Itoa(n), &SetOptions{Expires: true, TTL: time.Millisecond * 100}) return err } if err := db.SetConfig(config); err != nil { t.Fatal(err) } err := db.Update(func(tx *Tx) error { _, _, err := tx.Set("K", "0", &SetOptions{Expires: true, TTL: time.Millisecond * 100}) return err }) if err != nil { t.Fail() } done := make(chan struct{}) go func() { ticks := time.NewTicker(time.Millisecond * 50) defer ticks.Stop() for { select { case <-done: return case <-ticks.C: err := db.View(func(tx *Tx) error { v, err := tx.Get("K", true) if err != nil { return err } n, err := strconv.Atoi(v) if err != nil { return err } if n < 0 || n > 2 { t.Fail() } return nil }) if err != nil { t.Fail() } } } }() OUTER1: for { select { case <-time.After(time.Second * 2): t.Fail() case v := <-hits: if v >= 2 { break OUTER1 } } } err = db.View(func(tx *Tx) error { defer close(done) v, err := tx.Get("K") if err != nil { t.Fail() return err } if v != "2" { t.Fail() } return nil }) if err != nil { t.Fail() } } func TestTransactionLeak(t *testing.T) { // This tests an bug identified in Issue #69. When inside a Update // transaction, a Set after a Delete for a key that previously exists will // remove the key when the transaction was rolledback. buntDB, err := Open(":memory:") if err != nil { t.Fatal(err) } bd := buntDB err = bd.Update(func(tx *Tx) error { _, _, err := tx.Set("a", "a", nil) return err }) if err != nil { t.Fatal(err) } bd.View(func(tx *Tx) error { val, err := tx.Get("a") if err != nil { return err } if val != "a" { return errors.New("mismatch") } return nil }) if err != nil { t.Fatal(err) } bd.Update(func(tx *Tx) error { val, err := tx.Delete("a") if err != nil { return err } if val != "a" { return errors.New("mismatch") } val, err = tx.Get("a") if err != ErrNotFound { return fmt.Errorf("expected NotFound, got %v", err) } if val != "" { return errors.New("mismatch") } val, rep, err := tx.Set("a", "b", nil) if err != nil { return err } if rep { return errors.New("replaced") } if val != "" { return errors.New("mismatch") } val, err = tx.Get("a") if err != nil { return err } if val != "b" { return errors.New("mismatch") } return errors.New("rollback") }) if err != nil { t.Fatal(err) } bd.View(func(tx *Tx) error { val, err := tx.Get("a") if err != nil { return err } if val != "a" { return errors.New("mismatch") } return nil }) if err != nil { t.Fatal(err) } } func TestReloadNotInvalid(t *testing.T) { rand.Seed(time.Now().UnixNano()) os.RemoveAll("data.db") defer os.RemoveAll("data.db") start := time.Now() ii := 0 for time.Since(start) < time.Second*5 { func() { db, err := Open("data.db") if err != nil { t.Fatal(err) } defer func() { if err := db.Close(); err != nil { panic(err) } // truncate at a random point in the file f, err := os.OpenFile("data.db", os.O_RDWR, 0666) if err != nil { panic(err) } defer f.Close() sz, err := f.Seek(0, 2) if err != nil { panic(err) } n := sz/2 + int64(rand.Intn(int(sz/2))) err = f.Truncate(n) if err != nil { panic(err) } }() N := 500 lotsa.Ops(N, 16, func(i, t int) { if i == N/2 && ii&7 == 0 { if err := db.Shrink(); err != nil { panic(err) } } err := db.Update(func(tx *Tx) error { _, _, err := tx.Set(fmt.Sprintf("key:%d", i), fmt.Sprintf("val:%d", i), &SetOptions{ Expires: true, TTL: 30, }) return err }) if err != nil { panic(err) } }) }() ii++ } } func TestEstSize(t *testing.T) { t.Run("estIntSize", func(t *testing.T) { assert.Assert(estIntSize(0) == 1) assert.Assert(estIntSize(1) == 1) assert.Assert(estIntSize(9) == 1) assert.Assert(estIntSize(10) == 2) assert.Assert(estIntSize(11) == 2) assert.Assert(estIntSize(19) == 2) assert.Assert(estIntSize(20) == 2) assert.Assert(estIntSize(113) == 3) assert.Assert(estIntSize(3822) == 4) assert.Assert(estIntSize(-1) == 2) assert.Assert(estIntSize(-12) == 3) assert.Assert(estIntSize(-124) == 4) }) } func TestWrappedError(t *testing.T) { defer func() { if err, ok := recover().(error); ok { if strings.HasPrefix(err.Error(), "buntdb: ") { err := errors.Unwrap(err) if err.Error() != "my fake error" { t.Fatal("!") } } } }() panicErr(errors.New("my fake error")) }