//nolint:goconst package mdbx import ( "bytes" "encoding/binary" "fmt" "runtime" "syscall" "testing" ) func TestTxn_ID(t *testing.T) { env := setup(t) var id0, id1, id2, id3 uintptr var txnInvalid *Txn err := env.View(func(txn *Txn) (err error) { id0 = txn.ID() return nil }) if err != nil { t.Error(err) } if id0 != 3 { t.Errorf("unexpected readonly id (before update): %v (!= %v)", id0, 3) } txnCached, err := env.BeginTxn(nil, Readonly) if err != nil { t.Error(err) return } defer txnCached.Abort() if txnCached.ID() != 3 { t.Errorf("unexpected readonly id (before update): %v (!= %v)", txnCached.ID(), 3) } if txnCached.getID() != txnCached.ID() { t.Errorf("unexpected readonly id (before update): %v (!= %v)", txnCached.ID(), txnCached.getID()) } txnCached.Reset() if txnCached.getID() != txnCached.ID() { t.Errorf("unexpected reset id: %v (!= %v)", txnCached.ID(), txnCached.getID()) } err = env.Update(func(txn *Txn) (err error) { dbi, err := txn.OpenDBI("test", Create, nil, nil) if err != nil { return err } id1 = txn.ID() return txn.Put(dbi, []byte("key"), []byte("val"), 0) }) if err != nil { t.Errorf("%v", err) return } err = env.View(func(txn *Txn) (err error) { id2 = txn.ID() txnInvalid = txn return nil }) if err != nil { t.Errorf("%v", err) return } id3 = txnInvalid.ID() // The ID of txnCached will actually change during the call to // txnCached.Renew(). It's imperative that any ID cached in the Txn object // does not diverge. if txnCached.ID() != txnCached.getID() { t.Errorf("unexpected invalid id: %v (!= %v)", txnCached.ID(), txnCached.getID()) } err = txnCached.Renew() if err != nil { t.Error(err) return } if txnCached.ID() != txnCached.getID() { t.Errorf("unexpected invalid id: %v (!= %v)", txnCached.ID(), txnCached.getID()) } t.Logf("ro txn id:: %v", id1) t.Logf("txn id: %v", id1) t.Logf("ro txn id: %v", id2) t.Logf("bad txn id: %v", id3) if id1 != id2 { t.Errorf("unexpected readonly id: %v (!= %v)", id2, id1) } if id3 != 0 { t.Errorf("unexpected invalid id: %v (!= %v)", id3, 0) } if id1 != txnCached.ID() { t.Errorf("unexpected invalid id: %v (!= %v)", txnCached.ID(), 0) } } func TestTxn_errLogf(t *testing.T) { env := setup(t) runtime.LockOSThread() defer runtime.UnlockOSThread() txn, err := env.BeginTxn(nil, 0) if err != nil { t.Error(err) } else { defer txn.Abort() txn.errf("this is just a test") } } func TestTxn_Drop(t *testing.T) { env := setup(t) db, err := openDBI(env, "db", Create) if err != nil { t.Error(err) return } err = env.Update(func(txn *Txn) (err error) { return txn.Put(db, []byte("k"), []byte("v"), 0) }) if err != nil { t.Error(err) return } err = env.Update(func(txn *Txn) (err error) { return txn.Drop(db, false) }) if err != nil { t.Error(err) return } err = env.View(func(txn *Txn) (err error) { _, err = txn.Get(db, []byte("k")) return err }) if !IsNotFound(err) { t.Error(err) return } err = env.Update(func(txn *Txn) (err error) { return txn.Drop(db, true) }) if err != nil { t.Error(err) return } err = env.View(func(txn *Txn) (err error) { _, err = txn.Get(db, []byte("k")) return err }) if !IsErrno(err, BadDBI) { t.Errorf("mdb_get: %v", err) } } func TestTxn_Del(t *testing.T) { env := setup(t) var db DBI err := env.Update(func(txn *Txn) (err error) { db, err = txn.OpenDBISimple("text", Create) return err }) if err != nil { panic(err) } err = env.Update(func(txn *Txn) (err error) { return txn.Put(db, []byte("k"), []byte("v"), 0) }) if err != nil { t.Error(err) } err = env.Update(func(txn *Txn) (err error) { return txn.Del(db, []byte("k"), nil) }) if err != nil { t.Error(err) } err = env.View(func(txn *Txn) (err error) { _, err = txn.Get(db, []byte("k")) return err }) if !IsNotFound(err) { t.Errorf("mdb_txn_get: %v", err) } } func TestTxn_Del_dup(t *testing.T) { env := setup(t) var db DBI err := env.Update(func(txn *Txn) (err error) { db, err = txn.OpenDBISimple("text", Create|DupSort) return err }) if err != nil { panic(err) } err = env.Update(func(txn *Txn) (err error) { return txn.Put(db, []byte("k"), []byte("v"), 0) }) if err != nil { t.Error(err) } err = env.Update(func(txn *Txn) (err error) { return txn.Del(db, []byte("k"), []byte("valignored")) }) if !IsNotFound(err) { t.Errorf("mdb_del: %v", err) } err = env.View(func(txn *Txn) (err error) { v, err := txn.Get(db, []byte("k")) if err != nil { return err } if string(v) != "v" { return fmt.Errorf("unexpected value: %q", v) } return nil }) if err != nil { t.Error(err) } } func TestTexn_Put_emptyValue(t *testing.T) { env := setup(t) var db DBI err := env.Update(func(txn *Txn) (err error) { db, err = txn.OpenDBISimple("text", Create) if err != nil { return err } err = txn.Put(db, []byte("k"), nil, 0) if err != nil { return err } v, err := txn.Get(db, []byte("k")) if err != nil { return err } if len(v) != 0 { t.Errorf("value: %q (!= \"\")", v) } return nil }) if err != nil { t.Error(err) return } } func TestTxn_PutReserve(t *testing.T) { env := setup(t) var db DBI err := env.Update(func(txn *Txn) (err error) { db, err = txn.OpenRoot(0) if err != nil { return err } val := "v" err = txn.Put(db, []byte("k"), []byte(val), 0) if err != nil { return err } p, err := txn.PutReserve(db, []byte("k"), len(val), 0) if err != nil { return err } copy(p, val) return nil }) if err != nil { t.Error(err) return } err = env.View(func(txn *Txn) (err error) { v, err := txn.Get(db, []byte("k")) if err != nil { return err } if string(v) != "v" { return fmt.Errorf("value: %q", v) } return nil }) if err != nil { t.Error(err) return } } func TestTxn_bytesBuffer(t *testing.T) { env := setup(t) db, err := openRoot(env, 0) if err != nil { t.Error(err) return } err = env.Update(func(txn *Txn) (err error) { k := new(bytes.Buffer) k.WriteString("hello") v := new(bytes.Buffer) v.WriteString("world") return txn.Put(db, k.Bytes(), v.Bytes(), 0) }) if err != nil { t.Error(err) return } err = env.View(func(txn *Txn) (err error) { k := new(bytes.Buffer) k.WriteString("hello") v, err := txn.Get(db, k.Bytes()) if err != nil { return err } if !bytes.Equal(v, []byte("world")) { return fmt.Errorf("unexpected value: %q", v) } return nil }) if err != nil { t.Error(err) return } } func TestTxn_Put_overwrite(t *testing.T) { env := setup(t) db, err := openRoot(env, 0) if err != nil { t.Error(err) return } err = env.Update(func(txn *Txn) (err error) { k := []byte("hello") v := []byte("world") err = txn.Put(db, k, v, 0) if err != nil { return err } copy(k, "bye!!") copy(v, "toodles") err = txn.Put(db, k, v, 0) if err != nil { return err } return nil }) if err != nil { t.Error(err) return } err = env.View(func(txn *Txn) (err error) { v, err := txn.Get(db, []byte("hello")) if err != nil { return err } if !bytes.Equal(v, []byte("world")) { return fmt.Errorf("unexpected value") } return nil }) if err != nil { t.Error(err) return } } func TestTxn_OpenDBI_emptyName(t *testing.T) { env := setup(t) err := env.View(func(txn *Txn) (err error) { _, err = txn.OpenDBISimple("", 0) return err }) if !IsErrno(err, NotFound) { t.Errorf("mdb_dbi_open: %v", err) } err = env.View(func(txn *Txn) (err error) { _, err = txn.OpenDBISimple("", Create) return err }) if runtime.GOOS == "windows" { if !IsErrnoSys(err, syscall.EIO) { t.Errorf("mdb_dbi_open: %v", err) } } else { if !IsErrnoSys(err, syscall.EACCES) { t.Errorf("mdb_dbi_open: %v", err) } } } func TestTxn_OpenDBI_zero(t *testing.T) { env := setup(t) err := env.View(func(txn *Txn) (err error) { _, err = txn.OpenRoot(0) if err != nil { return err } _, err = txn.Get(0, []byte("k")) return err }) if !IsErrno(err, BadDBI) { t.Errorf("mdb_dbi_open: %v", err) } } func TestTxn_Commit_managed(t *testing.T) { env := setup(t) err := env.View(func(txn *Txn) (err error) { defer func() { if e := recover(); e == nil { t.Errorf("expected panic: %v", err) } }() _, err2 := txn.Commit() return err2 }) if err != nil { t.Error(err) } err = env.View(func(txn *Txn) (err error) { defer func() { if e := recover(); e == nil { t.Errorf("expected panic: %v", err) } }() txn.Abort() return fmt.Errorf("abort") }) if err != nil { t.Error(err) } err = env.View(func(txn *Txn) (err error) { defer func() { if e := recover(); e == nil { t.Errorf("expected panic: %v", err) } }() return txn.Renew() }) if err != nil { t.Error(err) } err = env.View(func(txn *Txn) (err error) { defer func() { if e := recover(); e == nil { t.Errorf("expected panic: %v", err) } }() txn.Reset() return fmt.Errorf("reset") }) if err != nil { t.Error(err) } } func TestTxn_Commit(t *testing.T) { if runtime.GOOS == "windows" { t.Skip("fix me") } env := setup(t) runtime.LockOSThread() defer runtime.UnlockOSThread() txn, err := env.BeginTxn(nil, 0) if err != nil { t.Error(err) return } txn.Abort() _, err = txn.Commit() if !IsErrnoSys(err, syscall.EINVAL) { t.Errorf("mdb_txn_commit: %s", err.Error()) } } func TestTxn_Update(t *testing.T) { env := setup(t) var db DBI err := env.Update(func(txn *Txn) (err error) { db, err = txn.OpenRoot(Create) if err != nil { return err } err = txn.Put(db, []byte("mykey"), []byte("myvalue"), 0) if err != nil { return err } return nil }) if err != nil { t.Errorf("update: %v", err) return } err = env.View(func(txn *Txn) (err error) { v, err := txn.Get(db, []byte("mykey")) if err != nil { return err } if string(v) != "myvalue" { return fmt.Errorf("value: %q", v) } return nil }) if err != nil { t.Errorf("view: %v", err) return } } func TestTxn_Flags(t *testing.T) { env := setup(t) path, err := env.Path() if err != nil { t.Fatal(err) } dbflags := uint(ReverseKey | ReverseDup | DupSort | DupFixed) err = env.Update(func(txn *Txn) (err error) { db, err := txn.OpenDBI("testdb", dbflags|Create, nil, nil) if err != nil { return err } err = txn.Put(db, []byte("bcd"), []byte("exval1"), 0) if err != nil { return err } err = txn.Put(db, []byte("abcda"), []byte("exval3"), 0) if err != nil { return err } err = txn.Put(db, []byte("abcda"), []byte("exval2"), 0) if err != nil { return err } cur, err := txn.OpenCursor(db) if err != nil { return err } defer cur.Close() k, v, err := cur.Get(nil, nil, Next) if err != nil { return err } if string(k) != "abcda" { // ReverseKey does not do what one might expect return fmt.Errorf("unexpected first key: %q", k) } if string(v) != "exval2" { return fmt.Errorf("unexpected first value: %q", v) } return nil }) env.Close() if err != nil { t.Error(err) return } // opening the database after it is created inherits the original flags. env, err = NewEnv() if err != nil { t.Error(err) return } err = env.SetOption(OptMaxDB, uint64(1)) if err != nil { t.Error(err) return } defer env.Close() err = env.Open(path, 0, 0644) if err != nil { t.Error(err) return } err = env.View(func(txn *Txn) (err error) { db, err := txn.OpenDBI("testdb", 0, nil, nil) if err != nil { return err } flags, err := txn.Flags(db) if err != nil { return err } if flags != dbflags { return fmt.Errorf("unexpected flags") } cur, err := txn.OpenCursor(db) if err != nil { return err } k, v, err := cur.Get(nil, nil, Next) if err != nil { return err } if string(k) != "abcda" { return fmt.Errorf("unexpected first key: %q", k) } if string(v) != "exval2" { return fmt.Errorf("unexpected first value: %q", v) } return nil }) if err != nil { t.Error(err) return } } func TestTxn_Renew(t *testing.T) { env := setup(t) // It is not necessary to call runtime.LockOSThread in this test because // the only unmanaged Txn is Readonly. var dbroot DBI err := env.Update(func(txn *Txn) (err error) { dbroot, err = txn.OpenRoot(0) return err }) if err != nil { t.Error(err) return } txn, err := env.BeginTxn(nil, Readonly) if err != nil { t.Error(err) return } defer txn.Abort() _, err = txn.Get(dbroot, []byte("k")) if !IsNotFound(err) { t.Errorf("get: %v", err) } err = env.Update(func(txn *Txn) (err error) { dbroot, err = txn.OpenRoot(0) if err != nil { return err } return txn.Put(dbroot, []byte("k"), []byte("v"), 0) }) if err != nil { t.Error(err) } _, err = txn.Get(dbroot, []byte("k")) if !IsNotFound(err) { t.Errorf("get: %v", err) } txn.Reset() err = txn.Renew() if err != nil { t.Error(err) } val, err := txn.Get(dbroot, []byte("k")) if err != nil { t.Error(err) } if string(val) != "v" { t.Errorf("unexpected value: %q", val) } } func TestTxn_Reset_doubleReset(t *testing.T) { env := setup(t) txn, err := env.BeginTxn(nil, Readonly) if err != nil { t.Error(err) return } defer txn.Abort() txn.Reset() txn.Reset() } // This test demonstrates that Reset/Renew have no effect on writable // transactions. The transaction may be committed after Reset/Renew are called. func TestTxn_Reset_writeTxn(t *testing.T) { env := setup(t) runtime.LockOSThread() defer runtime.UnlockOSThread() txn, err := env.BeginTxn(nil, 0) if err != nil { t.Error(err) return } defer txn.Abort() db, err := txn.OpenRoot(0) if err != nil { t.Error(err) } err = txn.Put(db, []byte("k"), []byte("v"), 0) if err != nil { t.Error(err) } // Reset is a noop and Renew will always error out. txn.Reset() err = txn.Renew() if runtime.GOOS == "windows" { // todo } else { if !IsErrnoSys(err, syscall.EINVAL) { t.Errorf("renew: %v", err) } } _, err = txn.Commit() if err != nil { t.Errorf("commit: %v", err) } err = env.View(func(txn *Txn) (err error) { val, err := txn.Get(db, []byte("k")) if err != nil { return err } if string(val) != "v" { return fmt.Errorf("unexpected value: %q", val) } return nil }) if err != nil { t.Error(err) } } func TestTxn_UpdateLocked(t *testing.T) { env := setup(t) runtime.LockOSThread() defer runtime.UnlockOSThread() var dbi DBI err := env.UpdateLocked(func(txn *Txn) (err error) { dbi, err = txn.OpenRoot(0) if err != nil { return err } return txn.Put(dbi, []byte("k0"), []byte("v0"), 0) }) if err != nil { t.Error(err) } err = env.View(func(txn *Txn) (err error) { v, err := txn.Get(dbi, []byte("k0")) if err != nil { return err } if string(v) != "v0" { return fmt.Errorf("unexpected value: %q (!= %q)", v, "v0") } return nil }) if err != nil { t.Error(err) } } func TestTxn_RunTxn(t *testing.T) { env := setup(t) var dbi DBI err := env.RunTxn(0, func(txn *Txn) (err error) { dbi, err = txn.OpenRoot(0) if err != nil { return err } return txn.Put(dbi, []byte("k0"), []byte("v0"), 0) }) if err != nil { t.Error(err) } err = env.RunTxn(Readonly, func(txn *Txn) (err error) { v, err := txn.Get(dbi, []byte("k0")) if err != nil { return err } if string(v) != "v0" { return fmt.Errorf("unexpected value: %q (!= %q)", v, "v0") } err = txn.Put(dbi, []byte("k1"), []byte("v1"), 0) if err == nil { return fmt.Errorf("allowed to Put in a readonly Txn") } return nil }) if err != nil { t.Error(err) } } func TestTxn_Stat(t *testing.T) { env := setup(t) var dbi DBI err := env.Update(func(txn *Txn) (err error) { dbi, err = txn.OpenDBISimple("testdb", Create) return err }) if err != nil { t.Errorf("%s", err) return } err = env.Update(func(txn *Txn) (err error) { put := func(k, v []byte) { if err == nil { err = txn.Put(dbi, k, v, 0) } } put([]byte("a"), []byte("1")) put([]byte("b"), []byte("2")) put([]byte("c"), []byte("3")) return err }) if err != nil { t.Errorf("%s", err) } var stat *Stat err = env.View(func(txn *Txn) (err error) { stat, err = txn.StatDBI(dbi) return err }) if err != nil { t.Errorf("%s", err) return } if stat.Entries != 3 { t.Errorf("unexpected entries: %d (expected %d)", stat.Entries, 3) } } func TestTxn_StatOnEmpty(t *testing.T) { env := setup(t) var dbi DBI err := env.Update(func(txn *Txn) (err error) { dbi, err = txn.OpenDBISimple("testdb", Create|DupSort) return err }) if err != nil { t.Errorf("%s", err) return } env.CloseDBI(dbi) err = env.Update(func(txn *Txn) (err error) { dbi, err = txn.OpenDBISimple("testdb", 0) return err }) if err != nil { t.Errorf("%s", err) return } err = env.Update(func(txn *Txn) (err error) { err = txn.Drop(dbi, true) return err }) if err != nil { t.Errorf("%s", err) return } } func TestSequence(t *testing.T) { env := setup(t) var dbi1 DBI var dbi2 DBI err := env.Update(func(txn *Txn) (err error) { dbi1, err = txn.OpenDBISimple("testdb", Create) if err != nil { return err } dbi2, err = txn.OpenDBISimple("testdb2", Create) return err }) if err != nil { t.Errorf("%s", err) return } err = env.Update(func(txn *Txn) (err error) { v, err := txn.Sequence(dbi1, 0) // 0 accepted, validate on app level if err != nil { return err } if v != 0 { t.Errorf("unexpected value: %d (expected %d)", v, 0) } v, err = txn.Sequence(dbi2, 2) if err != nil { return err } if v != 0 { t.Errorf("unexpected value: %d (expected %d)", v, 1) } v, err = txn.Sequence(dbi1, 3) if err != nil { return err } if v != 0 { t.Errorf("unexpected value: %d (expected %d)", 0, 0) } return nil }) if err != nil { t.Errorf("%s", err) } err = env.View(func(txn *Txn) (err error) { v, err := txn.Sequence(dbi1, 0) if err != nil { return err } if v != 3 { t.Errorf("unexpected value: %d (expected %d)", v, 3) } _, err = txn.Sequence(dbi1, 3) // error if > 0 in read tx if err == nil { t.Errorf("error expected") } return nil }) if err != nil { t.Errorf("%s", err) return } } func BenchmarkTxn_abort(b *testing.B) { env := setup(b) var e = fmt.Errorf("abort") b.ResetTimer() for i := 0; i < b.N; i++ { _ = env.Update(func(txn *Txn) error { return e }) } } func BenchmarkTxn_commit(b *testing.B) { env := setup(b) var db DBI err := env.Update(func(txn *Txn) (err error) { db, err = txn.OpenDBISimple("testdb", Create) if err != nil { return err } return err }) if err != nil { b.Errorf("%s", err) return } b.ResetTimer() for i := 0; i < b.N; i++ { var k [8]byte binary.BigEndian.PutUint64(k[:], uint64(i)) err = env.Update(func(txn *Txn) error { err = txn.Put(db, k[:], k[:], 0) if err != nil { return err } return nil }) if err != nil { b.Error(err) return } } } func BenchmarkTxn_ro(b *testing.B) { env := setup(b) b.ResetTimer() for i := 0; i < b.N; i++ { err := env.View(func(txn *Txn) error { return nil }) if err != nil { b.Error(err) return } } } func BenchmarkTxn_unmanaged_abort(b *testing.B) { env := setup(b) runtime.LockOSThread() defer runtime.UnlockOSThread() b.ResetTimer() for i := 0; i < b.N; i++ { txn, err := env.BeginTxn(nil, 0) if err != nil { b.Error(err) return } txn.Abort() } } func BenchmarkTxn_unmanaged_commit(b *testing.B) { env := setup(b) runtime.LockOSThread() defer runtime.UnlockOSThread() b.ResetTimer() for i := 0; i < b.N; i++ { txn, err := env.BeginTxn(nil, 0) if err != nil { b.Error(err) return } txn.Abort() } } func BenchmarkTxn_unmanaged_ro(b *testing.B) { env := setup(b) // It is not necessary to call runtime.LockOSThread here because the txn is // Readonly b.ResetTimer() for i := 0; i < b.N; i++ { txn, err := env.BeginTxn(nil, Readonly) if err != nil { b.Error(err) return } txn.Abort() } } func BenchmarkTxn_renew(b *testing.B) { env := setup(b) // It is not necessary to call runtime.LockOSThread here because the txn is // Readonly txn, err := env.BeginTxn(nil, Readonly) if err != nil { b.Error(err) return } defer txn.Abort() b.ResetTimer() for i := 0; i < b.N; i++ { txn.Reset() err = txn.Renew() if err != nil { b.Error(err) return } } } func BenchmarkTxn_Put_append(b *testing.B) { env := setup(b) err := env.SetGeometry(-1, -1, 1024*1024, -1, -1, 4096) if err != nil { b.Error(err) return } var db DBI err = env.Update(func(txn *Txn) (err error) { db, err = txn.OpenRoot(0) return err }) if err != nil { b.Errorf("dbi: %v", err) return } b.ResetTimer() err = env.Update(func(txn *Txn) (err error) { for i := 0; i < b.N; i++ { var k [8]byte binary.BigEndian.PutUint64(k[:], uint64(i)) err = txn.Put(db, k[:], k[:], Append) if err != nil { return err } } b.StopTimer() defer b.StartTimer() return txn.Drop(db, false) }) if err != nil { b.Errorf("put: %v", err) } } func BenchmarkTxn_Put_append_noflag(b *testing.B) { env := setup(b) err := env.SetGeometry(-1, -1, 1024*1024, -1, -1, 4096) if err != nil { b.Fatalf("Cannot set mapsize: %s", err) } var db DBI err = env.Update(func(txn *Txn) (err error) { db, err = txn.OpenRoot(0) return err }) if err != nil { b.Errorf("dbi: %v", err) return } err = env.Update(func(txn *Txn) (err error) { for i := 0; i < b.N; i++ { var k [8]byte binary.BigEndian.PutUint64(k[:], uint64(i)) err = txn.Put(db, k[:], k[:], 0) if err != nil { return err } } b.StopTimer() defer b.StartTimer() return txn.Drop(db, false) }) if err != nil { b.Errorf("put: %v", err) } } func openRoot(env *Env, flags uint) (DBI, error) { var db DBI dotxn := env.View if flags != 0 { dotxn = env.Update } err := dotxn(func(txn *Txn) (err error) { db, err = txn.OpenRoot(flags) return err }) if err != nil { return 0, err } return db, nil } func openDBI(env *Env, key string, flags uint) (DBI, error) { var db DBI err := env.Update(func(txn *Txn) (err error) { db, err = txn.OpenDBI(key, flags, nil, nil) return err }) if err != nil { return 0, err } return db, nil } func TestNameToNano(t *testing.T) { if toDuration(65536).Milliseconds() != 1_000 { t.Error("unexpected result") } if toDuration(65536*1_000).Milliseconds() != 1_000*1_000 { t.Error("unexpected result") } }