//nolint:goconst package mdbx import ( "bytes" "encoding/hex" "fmt" "os" "reflect" "runtime" "testing" ) func TestCursor_Txn(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 } cur, err := txn.OpenCursor(db) if err != nil { return err } _txn := cur.Txn() if _txn == nil { t.Errorf("nil cursor txn") } cur.Close() _txn = cur.Txn() if _txn != nil { t.Errorf("non-nil cursor txn") } return err }) if err != nil { t.Error(err) return } } func TestCursor_DBI(t *testing.T) { env := setup(t) err := env.Update(func(txn *Txn) (err error) { db, err := txn.OpenDBI("db", Create, nil, nil) if err != nil { return err } cur, err := txn.OpenCursor(db) if err != nil { return err } dbcur := cur.DBI() if dbcur != db { cur.Close() return fmt.Errorf("unequal db: %v != %v", dbcur, db) } cur.Close() dbcur = cur.DBI() if dbcur == db { return fmt.Errorf("db: %v", dbcur) } if dbcur != ^DBI(0) { return fmt.Errorf("db: %v", dbcur) } return nil }) if err != nil { t.Error(err) } } func TestCursor_Close(t *testing.T) { env := setup(t) runtime.LockOSThread() defer runtime.UnlockOSThread() txn, err := env.BeginTxn(nil, 0) if err != nil { t.Fatal(err) } defer txn.Abort() db, err := txn.OpenDBI("testing", Create, nil, nil) if err != nil { t.Fatal(err) } cur, err := txn.OpenCursor(db) if err != nil { t.Fatal(err) } cur.Close() cur.Close() err = cur.Put([]byte("closedput"), []byte("shouldfail"), 0) if err == nil { t.Fatalf("expected error: put on closed cursor") } } func TestCursor_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) { cur, err := txn.OpenCursor(db) if err != nil { return err } defer cur.Close() k := new(bytes.Buffer) k.WriteString("hello") v := new(bytes.Buffer) v.WriteString("world") return cur.Put(k.Bytes(), v.Bytes(), 0) }) if err != nil { t.Error(err) return } err = env.View(func(txn *Txn) (err error) { cur, err := txn.OpenCursor(db) if err != nil { return err } defer cur.Close() k := new(bytes.Buffer) k.WriteString("hello") _k, v, err := cur.Get(k.Bytes(), nil, SetKey) if err != nil { return err } if !bytes.Equal(_k, k.Bytes()) { return fmt.Errorf("unexpected key: %q", _k) } if !bytes.Equal(v, []byte("world")) { return fmt.Errorf("unexpected value: %q", v) } return nil }) if err != nil { t.Error(err) return } } func TestCursor_PutReserve(t *testing.T) { env := setup(t) var db DBI key := "reservekey" val := "reserveval" err := env.Update(func(txn *Txn) (err error) { db, err = txn.CreateDBI("testing") if err != nil { return err } cur, err := txn.OpenCursor(db) if err != nil { return err } defer cur.Close() p, err := cur.PutReserve([]byte(key), len(val), 0) if err != nil { return err } copy(p, val) return nil }) if err != nil { t.Fatal(err) } err = env.View(func(txn *Txn) (err error) { dbval, err := txn.Get(db, []byte(key)) if err != nil { return err } if !bytes.Equal(dbval, []byte(val)) { return fmt.Errorf("unexpected val %q != %q", dbval, val) } return nil }) if err != nil { t.Fatal(err) } } func TestCursor_Get_KV(t *testing.T) { env := setup(t) var dbi DBI err := env.Update(func(txn *Txn) (err error) { dbi, err = txn.OpenDBI("testdb", Create|DupSort, nil, nil) 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("k1"), []byte("v1")) put([]byte("k1"), []byte("v2")) put([]byte("k1"), []byte("v3")) return err }) if err != nil { t.Errorf("%s", err) } err = env.View(func(txn *Txn) (err error) { cur, err := txn.OpenCursor(dbi) if err != nil { return err } defer cur.Close() k, v, err := cur.Get([]byte("k1"), []byte("v0"), GetBothRange) if err != nil { return err } if string(k) != "k1" { t.Errorf("unexpected key: %q (not %q)", k, "k1") } if string(v) != "v1" { t.Errorf("unexpected value: %q (not %q)", k, "1") } _, _, err = cur.Get([]byte("k0"), []byte("v0"), GetBothRange) if !IsErrno(err, NotFound) { t.Errorf("unexpected error: %s", err) } _, _, err = cur.Get([]byte("k1"), []byte("v1"), GetBoth) return err }) if err != nil { t.Errorf("%s", err) } } func FromHex(in string) []byte { out, err := hex.DecodeString(in) if err != nil { panic(err) } return out } func TestDupCmpExcludeSuffix32(t *testing.T) { hash32Bytes := FromHex("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421") env := setup(t) var dbi DBI err := env.Update(func(txn *Txn) (err error) { dcmp := txn.GetCmpExcludeSuffix32() dbi, err = txn.OpenDBI("testdb", Create|DupSort, nil, dcmp) if err != nil { return err } if txn.DCmp(dbi, []byte{0}, append([]byte{0}, hash32Bytes...)) != 0 { t.Errorf("broken order") } if txn.DCmp(dbi, []byte{0, 0}, append([]byte{0}, hash32Bytes...)) != 1 { t.Errorf("broken order") } if txn.DCmp(dbi, hash32Bytes, append([]byte{0}, hash32Bytes...)) != -1 { t.Errorf("broken order") } return nil }) if err != nil { t.Errorf("%s", err) return } err = env.Update(func(txn *Txn) (err error) { err = txn.Put(dbi, []byte{0}, hash32Bytes, Append|AppendDup) if err != nil { panic(err) } err = txn.Put(dbi, []byte{0}, append([]byte{0}, hash32Bytes...), AppendDup) if err != nil { panic(err) } err = txn.Put(dbi, []byte{0}, append([]byte{0, 0}, hash32Bytes...), AppendDup) if err != nil { panic(err) } err = txn.Put(dbi, []byte{1}, hash32Bytes, Append|AppendDup) if err != nil { panic(err) } return err }) if err != nil { t.Errorf("%s", err) } err = env.View(func(txn *Txn) (err error) { cur, err := txn.OpenCursor(dbi) if err != nil { return err } defer cur.Close() k, v, err := cur.Get(nil, nil, Last) if err != nil { return err } if !bytes.Equal(k, []byte{1}) { t.Errorf("unexpected order: %x (not %x)", k, []byte{1}) } if !bytes.Equal(v, hash32Bytes) { t.Errorf("unexpected order: %x (not %x)", v, hash32Bytes) } _, _, err = cur.Get([]byte{0}, nil, First) if err != nil { return err } _, v, err = cur.Get(nil, nil, FirstDup) if err != nil { return err } if !bytes.Equal(v, hash32Bytes) { t.Errorf("unexpected order: %x (not %x)", v, hash32Bytes) } _, v, err = cur.Get(nil, nil, NextDup) if err != nil { return err } if !bytes.Equal(v, append([]byte{0}, hash32Bytes...)) { t.Errorf("unexpected order: %x (not %x)", v, append([]byte{0}, hash32Bytes...)) } _, v, err = cur.Get(nil, nil, NextDup) if err != nil { return err } if !bytes.Equal(v, append([]byte{0, 0}, hash32Bytes...)) { t.Errorf("unexpected order: %x (not %x)", v, append([]byte{0, 0}, hash32Bytes...)) } k, v, err = cur.Get(nil, nil, Next) if err != nil { return err } if !bytes.Equal(k, []byte{1}) { t.Errorf("unexpected order: %x (not %x)", k, []byte{1}) } if !bytes.Equal(v, hash32Bytes) { t.Errorf("unexpected order: %x (not %x)", v, hash32Bytes) } k, _, err = cur.Get([]byte{0}, []byte{40}, GetBothRange) if !IsNotFound(err) { t.Errorf("unexpected error: %v (key %x)", err, k) } _, v, err = cur.Get([]byte{0}, []byte{0}, GetBothRange) if err != nil { return err } if !bytes.Equal(v, append([]byte{0}, hash32Bytes...)) { t.Errorf("unexpected order: %x (not %x)", v, append([]byte{0}, hash32Bytes...)) } _, v, err = cur.Get([]byte{0}, nil, SetRange) if err != nil { return err } if !bytes.Equal(v, hash32Bytes) { t.Errorf("unexpected order: %x (not %x)", v, hash32Bytes) } return err }) if err != nil { t.Errorf("%s", err) } } func TestCursor_Get_op_Set_bytesBuffer(t *testing.T) { env := setup(t) var dbi DBI err := env.Update(func(txn *Txn) (err error) { dbi, err = txn.OpenDBI("testdb", Create|DupSort, nil, nil) 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("k1"), []byte("v11")) put([]byte("k1"), []byte("v12")) put([]byte("k1"), []byte("v13")) put([]byte("k2"), []byte("v21")) put([]byte("k2"), []byte("v22")) put([]byte("k2"), []byte("v23")) return err }) if err != nil { t.Errorf("%s", err) } err = env.View(func(txn *Txn) (err error) { cur, err := txn.OpenCursor(dbi) if err != nil { return err } defer cur.Close() // Create bytes.Buffer values containing a amount of bytes. Byte // slices returned from buf.Bytes() have a history of tricking the cgo // argument checker. var kbuf bytes.Buffer kbuf.WriteString("k2") k, _, err := cur.Get(kbuf.Bytes(), nil, Set) if err != nil { return err } if string(k) != kbuf.String() { t.Errorf("unexpected key: %q (not %q)", k, kbuf.String()) } // No guarantee is made about the return value of mdb_cursor_get when // MDB_SET is the op, so its value is not checked as part of this test. // That said, it is important that Cursor.Get not panic if given a // short buffer as an input value for a Set op (despite that not really // having any significance) var vbuf bytes.Buffer vbuf.WriteString("v22") k, _, err = cur.Get(kbuf.Bytes(), vbuf.Bytes(), Set) if err != nil { return err } if string(k) != kbuf.String() { t.Errorf("unexpected key: %q (not %q)", k, kbuf.String()) } return nil }) if err != nil { t.Errorf("%s", err) } } func TestCursor_Get_DupFixed(t *testing.T) { env := setup(t) const datasize = 16 pagesize := os.Getpagesize() numitems := (2 * pagesize / datasize) + 1 var dbi DBI key := []byte("key") err := env.Update(func(txn *Txn) (err error) { dbi, err = txn.OpenDBI("test", DupSort|DupFixed|Create, nil, nil) if err != nil { return err } for i := int64(0); i < int64(numitems); i++ { err = txn.Put(dbi, key, []byte(fmt.Sprintf("%016x", i)), 0) if err != nil { return err } } return nil }) if err != nil { t.Error(err) } var items [][]byte err = env.View(func(txn *Txn) (err error) { cur, err := txn.OpenCursor(dbi) if err != nil { return err } defer cur.Close() for { k, first, err := cur.Get(nil, nil, NextNoDup) if IsNotFound(err) { return nil } if err != nil { return err } if string(k) != string(key) { return fmt.Errorf("key: %s", k) } stride := len(first) for { _, v, err := cur.Get(nil, nil, NextMultiple) if IsNotFound(err) { break } if err != nil { return err } multi := WrapMulti(v, stride) for i := 0; i < multi.Len(); i++ { items = append(items, multi.Val(i)) } } } }) if err != nil { t.Error(err) } if len(items) != numitems { t.Errorf("unexpected number of items: %d (!= %d)", len(items), numitems) } for i, b := range items { expect := fmt.Sprintf("%016x", i) if string(b) != expect { t.Errorf("unexpected value: %q (!= %q)", b, expect) } } } func TestCursor_Get_reverse(t *testing.T) { env := setup(t) var dbi DBI err := env.Update(func(txn *Txn) (err error) { dbi, err = txn.OpenRoot(0) if err != nil { return err } err = txn.Put(dbi, []byte("k0"), []byte("v0"), 0) if err != nil { return err } err = txn.Put(dbi, []byte("k1"), []byte("v1"), 0) if err != nil { return err } return err }) if err != nil { t.Error(err) } type Item struct{ k, v []byte } var items []Item err = env.View(func(txn *Txn) (err error) { cur, err := txn.OpenCursor(dbi) if err != nil { return err } defer cur.Close() for { k, v, err := cur.Get(nil, nil, Prev) if IsNotFound(err) { return nil } if err != nil { return err } items = append(items, Item{k, v}) } }) if err != nil { t.Error(err) } expect := []Item{ {[]byte("k1"), []byte("v1")}, {[]byte("k0"), []byte("v0")}, } if !reflect.DeepEqual(items, expect) { t.Errorf("unexpected items %q (!= %q)", items, expect) } } //func TestCursor_PutMulti(t *testing.T) { // env := setup(t) // // // key := []byte("k") // items := [][]byte{ // []byte("v0"), // []byte("v2"), // []byte("v1"), // } // page := bytes.Join(items, nil) // stride := 2 // // var dbi DBI // err := env.Update(func(txn *Txn) (err error) { // dbi, err = txn.OpenDBISimple("test2", Create|DupSort|DupFixed) // if err != nil { // return err // } // // cur, err := txn.OpenCursor(dbi) // if err != nil { // return err // } // defer cur.Close() // // return cur.PutMulti(key, page, stride, 0) // }) // if err != nil { // t.Error(err) // } // // expect := [][]byte{ // []byte("v0"), // []byte("v1"), // []byte("v2"), // } // var dbitems [][]byte // err = env.View(func(txn *Txn) (err error) { // cur, err := txn.OpenCursor(dbi) // if err != nil { // return err // } // defer cur.Close() // // for { // k, v, err := cur.Get(nil, nil, Next) // if IsNotFound(err) { // return nil // } // if err != nil { // return err // } // if string(k) != "k" { // return fmt.Errorf("key: %q", k) // } // dbitems = append(dbitems, v) // } // }) // if err != nil { // t.Error(err) // } // if !reflect.DeepEqual(dbitems, expect) { // t.Errorf("unexpected items: %q (!= %q)", dbitems, items) // } //} func TestCursor_Del(t *testing.T) { env := setup(t) var db DBI type Item struct{ k, v string } items := []Item{ {"k0", "k0"}, {"k1", "k1"}, {"k2", "k2"}, } err := env.Update(func(txn *Txn) (err error) { db, err = txn.CreateDBI("testing") if err != nil { return err } for _, item := range items { err := txn.Put(db, []byte(item.k), []byte(item.v), 0) if err != nil { return err } } return nil }) if err != nil { t.Error(err) } err = env.Update(func(txn *Txn) (err error) { txn.RawRead = true cur, err := txn.OpenCursor(db) if err != nil { return err } item := items[1] k, v, err := cur.Get([]byte(item.k), nil, SetKey) if err != nil { return err } if !bytes.Equal(k, []byte(item.k)) { return fmt.Errorf("found key %q (!= %q)", k, item.k) } if !bytes.Equal(v, []byte(item.v)) { return fmt.Errorf("found value %q (!= %q)", k, item.v) } err = cur.Del(0) if err != nil { return err } k, v, err = cur.Get(nil, nil, Next) if err != nil { return fmt.Errorf("post-delete: %v", err) } item = items[2] if !bytes.Equal(k, []byte(item.k)) { return fmt.Errorf("found key %q (!= %q)", k, item.k) } if !bytes.Equal(v, []byte(item.v)) { return fmt.Errorf("found value %q (!= %q)", k, item.v) } return nil }) if err != nil { t.Error(err) } var newitems []Item err = env.View(func(txn *Txn) (err error) { txn.RawRead = true cur, err := txn.OpenCursor(db) if err != nil { return err } next := func(cur *Cursor) (k, v []byte, err error) { return cur.Get(nil, nil, Next) } for k, v, err := next(cur); !IsNotFound(err); k, v, err = next(cur) { if err != nil { return err } newitems = append(newitems, Item{string(k), string(v)}) } return nil }) if err != nil { t.Error(err) } expectitems := []Item{ items[0], items[2], } if !reflect.DeepEqual(newitems, expectitems) { t.Errorf("unexpected items %q (!= %q)", newitems, expectitems) } } // This test verifies the behavior of Cursor.Count when DupSort is provided. func TestCursor_Count_DupSort(t *testing.T) { env := setup(t) var db DBI err := env.Update(func(txn *Txn) (err error) { db, err = txn.OpenDBI("testingdup", Create|DupSort, nil, nil) if err != nil { return err } put := func(k, v string) { if err != nil { return } err = txn.Put(db, []byte(k), []byte(v), 0) } put("k", "v0") put("k", "v1") return err }) if err != nil { t.Error(err) } err = env.View(func(txn *Txn) (err error) { cur, err := txn.OpenCursor(db) if err != nil { return err } defer cur.Close() _, _, err = cur.Get(nil, nil, First) if err != nil { return err } numdup, err := cur.Count() if err != nil { return err } if numdup != 2 { t.Errorf("unexpected count: %d != %d", numdup, 2) } return nil }) if err != nil { t.Error(err) } } // This test verifies the behavior of Cursor.Count when DupSort is not enabled // on the database. func TestCursor_Count_noDupSort(t *testing.T) { env := setup(t) var db DBI err := env.Update(func(txn *Txn) (err error) { db, err = txn.OpenDBISimple("testingnodup", Create) if err != nil { return err } return txn.Put(db, []byte("k"), []byte("v1"), 0) }) if err != nil { t.Error(err) } // it is an error to call Count if the underlying database does not allow // duplicate keys. err = env.View(func(txn *Txn) (err error) { cur, err := txn.OpenCursor(db) if err != nil { return err } defer cur.Close() _, _, err = cur.Get(nil, nil, First) if err != nil { return err } _, err = cur.Count() if err != nil { t.Errorf("expected no error: %v", err) return nil } return nil }) if err != nil { t.Error(err) } } func TestCursor_Renew(t *testing.T) { env := setup(t) var db DBI err := env.Update(func(txn *Txn) (err error) { db, err = txn.OpenRoot(0) return err }) if err != nil { t.Error(err) return } err = env.Update(func(txn *Txn) (err error) { put := func(k, v string) { if err == nil { err = txn.Put(db, []byte(k), []byte(v), 0) } } put("k1", "v1") put("k2", "v2") put("k3", "v3") return err }) if err != nil { t.Error("err") } var cur *Cursor err = env.View(func(txn *Txn) (err error) { 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) != "k1" { return fmt.Errorf("key: %q", k) } if string(v) != "v1" { return fmt.Errorf("val: %q", v) } return nil }) if err != nil { t.Error(err) } err = env.View(func(txn *Txn) (err error) { err = cur.Renew(txn) if err != nil { return err } k, v, err := cur.Get(nil, nil, Next) if err != nil { return err } if string(k) != "k1" { return fmt.Errorf("key: %q", k) } if string(v) != "v1" { return fmt.Errorf("val: %q", v) } return nil }) if err != nil { t.Error(err) } } func BenchmarkCursor(b *testing.B) { env := setup(b) var db DBI err := env.View(func(txn *Txn) (err error) { db, err = txn.OpenRoot(0) if err != nil { return err } return nil }) if err != nil { b.Error(err) return } err = env.View(func(txn *Txn) (err error) { b.ResetTimer() defer b.StopTimer() for i := 0; i < b.N; i++ { cur, err := txn.OpenCursor(db) if err != nil { return err } cur.Close() } return }) if err != nil { b.Error(err) return } } func BenchmarkCursor_Renew(b *testing.B) { env := setup(b) var cur *Cursor err := env.View(func(txn *Txn) (err error) { db, err := txn.OpenRoot(0) if err != nil { return err } cur, err = txn.OpenCursor(db) return err }) if err != nil { b.Error(err) return } _ = env.View(func(txn *Txn) (err error) { b.ResetTimer() defer b.StopTimer() for i := 0; i < b.N; i++ { err = cur.Renew(txn) if err != nil { return err } } return nil }) }