| /* |
| * Copyright 2017 Dgraph Labs, Inc. and Contributors |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package badger |
| |
| import ( |
| "fmt" |
| "io/ioutil" |
| "math/rand" |
| "strconv" |
| "sync" |
| "testing" |
| "time" |
| |
| "github.com/dgraph-io/badger/v2/options" |
| "github.com/dgraph-io/badger/v2/y" |
| |
| "github.com/stretchr/testify/require" |
| ) |
| |
| func TestTxnSimple(t *testing.T) { |
| runBadgerTest(t, nil, func(t *testing.T, db *DB) { |
| txn := db.NewTransaction(true) |
| |
| for i := 0; i < 10; i++ { |
| k := []byte(fmt.Sprintf("key=%d", i)) |
| v := []byte(fmt.Sprintf("val=%d", i)) |
| require.NoError(t, txn.SetEntry(NewEntry(k, v))) |
| } |
| |
| item, err := txn.Get([]byte("key=8")) |
| require.NoError(t, err) |
| |
| require.NoError(t, item.Value(func(val []byte) error { |
| require.Equal(t, []byte("val=8"), val) |
| return nil |
| })) |
| |
| require.Panics(t, func() { txn.CommitAt(100, nil) }) |
| require.NoError(t, txn.Commit()) |
| }) |
| } |
| |
| func TestTxnReadAfterWrite(t *testing.T) { |
| test := func(t *testing.T, db *DB) { |
| var wg sync.WaitGroup |
| N := 100 |
| wg.Add(N) |
| for i := 0; i < N; i++ { |
| go func(i int) { |
| defer wg.Done() |
| key := []byte(fmt.Sprintf("key%d", i)) |
| err := db.Update(func(tx *Txn) error { |
| return tx.SetEntry(NewEntry(key, key)) |
| }) |
| require.NoError(t, err) |
| err = db.View(func(tx *Txn) error { |
| item, err := tx.Get(key) |
| require.NoError(t, err) |
| val, err := item.ValueCopy(nil) |
| require.NoError(t, err) |
| require.Equal(t, val, key) |
| return nil |
| }) |
| require.NoError(t, err) |
| }(i) |
| } |
| wg.Wait() |
| } |
| t.Run("disk mode", func(t *testing.T) { |
| runBadgerTest(t, nil, func(t *testing.T, db *DB) { |
| test(t, db) |
| }) |
| }) |
| t.Run("InMemory mode", func(t *testing.T) { |
| opt := getTestOptions("") |
| opt.InMemory = true |
| db, err := Open(opt) |
| require.NoError(t, err) |
| test(t, db) |
| require.NoError(t, db.Close()) |
| }) |
| } |
| |
| func TestTxnCommitAsync(t *testing.T) { |
| key := func(i int) []byte { |
| return []byte(fmt.Sprintf("key=%d", i)) |
| } |
| |
| runBadgerTest(t, nil, func(t *testing.T, db *DB) { |
| txn := db.NewTransaction(true) |
| for i := 0; i < 40; i++ { |
| err := txn.SetEntry(NewEntry(key(i), []byte(strconv.Itoa(100)))) |
| require.NoError(t, err) |
| } |
| require.NoError(t, txn.Commit()) |
| txn.Discard() |
| |
| closer := y.NewCloser(1) |
| go func() { |
| defer closer.Done() |
| for { |
| select { |
| case <-closer.HasBeenClosed(): |
| return |
| default: |
| } |
| // Keep checking balance variant |
| txn := db.NewTransaction(false) |
| totalBalance := 0 |
| for i := 0; i < 40; i++ { |
| item, err := txn.Get(key(i)) |
| require.NoError(t, err) |
| val, err := item.ValueCopy(nil) |
| require.NoError(t, err) |
| bal, err := strconv.Atoi(string(val)) |
| require.NoError(t, err) |
| totalBalance += bal |
| } |
| require.Equal(t, totalBalance, 4000) |
| txn.Discard() |
| } |
| }() |
| |
| var wg sync.WaitGroup |
| wg.Add(100) |
| for i := 0; i < 100; i++ { |
| go func() { |
| txn := db.NewTransaction(true) |
| delta := rand.Intn(100) |
| for i := 0; i < 20; i++ { |
| err := txn.SetEntry(NewEntry(key(i), []byte(strconv.Itoa(100-delta)))) |
| require.NoError(t, err) |
| } |
| for i := 20; i < 40; i++ { |
| err := txn.SetEntry(NewEntry(key(i), []byte(strconv.Itoa(100+delta)))) |
| require.NoError(t, err) |
| } |
| // We are only doing writes, so there won't be any conflicts. |
| txn.CommitWith(func(err error) {}) |
| txn.Discard() |
| wg.Done() |
| }() |
| } |
| wg.Wait() |
| closer.SignalAndWait() |
| time.Sleep(time.Millisecond * 10) // allow goroutine to complete. |
| }) |
| } |
| |
| func TestTxnVersions(t *testing.T) { |
| runBadgerTest(t, nil, func(t *testing.T, db *DB) { |
| k := []byte("key") |
| for i := 1; i < 10; i++ { |
| txn := db.NewTransaction(true) |
| |
| require.NoError(t, txn.SetEntry(NewEntry(k, []byte(fmt.Sprintf("valversion=%d", i))))) |
| require.NoError(t, txn.Commit()) |
| require.Equal(t, uint64(i), db.orc.readTs()) |
| } |
| |
| checkIterator := func(itr *Iterator, i int) { |
| defer itr.Close() |
| count := 0 |
| for itr.Rewind(); itr.Valid(); itr.Next() { |
| item := itr.Item() |
| require.Equal(t, k, item.Key()) |
| |
| val, err := item.ValueCopy(nil) |
| require.NoError(t, err) |
| exp := fmt.Sprintf("valversion=%d", i) |
| require.Equal(t, exp, string(val), "i=%d", i) |
| count++ |
| } |
| require.Equal(t, 1, count, "i=%d", i) // Should only loop once. |
| } |
| |
| checkAllVersions := func(itr *Iterator, i int) { |
| var version uint64 |
| if itr.opt.Reverse { |
| version = 1 |
| } else { |
| version = uint64(i) |
| } |
| |
| count := 0 |
| for itr.Rewind(); itr.Valid(); itr.Next() { |
| item := itr.Item() |
| require.Equal(t, k, item.Key()) |
| require.Equal(t, version, item.Version()) |
| |
| val, err := item.ValueCopy(nil) |
| require.NoError(t, err) |
| exp := fmt.Sprintf("valversion=%d", version) |
| require.Equal(t, exp, string(val), "v=%d", version) |
| count++ |
| |
| if itr.opt.Reverse { |
| version++ |
| } else { |
| version-- |
| } |
| } |
| require.Equal(t, i, count, "i=%d", i) // Should loop as many times as i. |
| } |
| |
| for i := 1; i < 10; i++ { |
| txn := db.NewTransaction(true) |
| txn.readTs = uint64(i) // Read version at i. |
| |
| item, err := txn.Get(k) |
| require.NoError(t, err) |
| |
| val, err := item.ValueCopy(nil) |
| require.NoError(t, err) |
| require.Equal(t, []byte(fmt.Sprintf("valversion=%d", i)), val, |
| "Expected versions to match up at i=%d", i) |
| |
| // Try retrieving the latest version forward and reverse. |
| itr := txn.NewIterator(DefaultIteratorOptions) |
| checkIterator(itr, i) |
| |
| opt := DefaultIteratorOptions |
| opt.Reverse = true |
| itr = txn.NewIterator(opt) |
| checkIterator(itr, i) |
| |
| // Now try retrieving all versions forward and reverse. |
| opt = DefaultIteratorOptions |
| opt.AllVersions = true |
| itr = txn.NewIterator(opt) |
| checkAllVersions(itr, i) |
| itr.Close() |
| |
| opt = DefaultIteratorOptions |
| opt.AllVersions = true |
| opt.Reverse = true |
| itr = txn.NewIterator(opt) |
| checkAllVersions(itr, i) |
| itr.Close() |
| |
| txn.Discard() |
| } |
| txn := db.NewTransaction(true) |
| defer txn.Discard() |
| item, err := txn.Get(k) |
| require.NoError(t, err) |
| |
| val, err := item.ValueCopy(nil) |
| require.NoError(t, err) |
| require.Equal(t, []byte("valversion=9"), val) |
| }) |
| } |
| |
| func TestTxnWriteSkew(t *testing.T) { |
| runBadgerTest(t, nil, func(t *testing.T, db *DB) { |
| // Accounts |
| ax := []byte("x") |
| ay := []byte("y") |
| |
| // Set balance to $100 in each account. |
| txn := db.NewTransaction(true) |
| defer txn.Discard() |
| val := []byte(strconv.Itoa(100)) |
| require.NoError(t, txn.SetEntry(NewEntry(ax, val))) |
| require.NoError(t, txn.SetEntry(NewEntry(ay, val))) |
| require.NoError(t, txn.Commit()) |
| require.Equal(t, uint64(1), db.orc.readTs()) |
| |
| getBal := func(txn *Txn, key []byte) (bal int) { |
| item, err := txn.Get(key) |
| require.NoError(t, err) |
| |
| val, err := item.ValueCopy(nil) |
| require.NoError(t, err) |
| bal, err = strconv.Atoi(string(val)) |
| require.NoError(t, err) |
| return bal |
| } |
| |
| // Start two transactions, each would read both accounts and deduct from one account. |
| txn1 := db.NewTransaction(true) |
| |
| sum := getBal(txn1, ax) |
| sum += getBal(txn1, ay) |
| require.Equal(t, 200, sum) |
| require.NoError(t, txn1.SetEntry(NewEntry(ax, []byte("0")))) // Deduct 100 from ax. |
| |
| // Let's read this back. |
| sum = getBal(txn1, ax) |
| require.Equal(t, 0, sum) |
| sum += getBal(txn1, ay) |
| require.Equal(t, 100, sum) |
| // Don't commit yet. |
| |
| txn2 := db.NewTransaction(true) |
| |
| sum = getBal(txn2, ax) |
| sum += getBal(txn2, ay) |
| require.Equal(t, 200, sum) |
| require.NoError(t, txn2.SetEntry(NewEntry(ay, []byte("0")))) // Deduct 100 from ay. |
| |
| // Let's read this back. |
| sum = getBal(txn2, ax) |
| require.Equal(t, 100, sum) |
| sum += getBal(txn2, ay) |
| require.Equal(t, 100, sum) |
| |
| // Commit both now. |
| require.NoError(t, txn1.Commit()) |
| require.Error(t, txn2.Commit()) // This should fail. |
| |
| require.Equal(t, uint64(2), db.orc.readTs()) |
| }) |
| } |
| |
| // a3, a2, b4 (del), b3, c2, c1 |
| // Read at ts=4 -> a3, c2 |
| // Read at ts=4(Uncommitted) -> a3, b4 |
| // Read at ts=3 -> a3, b3, c2 |
| // Read at ts=2 -> a2, c2 |
| // Read at ts=1 -> c1 |
| func TestTxnIterationEdgeCase(t *testing.T) { |
| runBadgerTest(t, nil, func(t *testing.T, db *DB) { |
| ka := []byte("a") |
| kb := []byte("b") |
| kc := []byte("c") |
| |
| // c1 |
| txn := db.NewTransaction(true) |
| require.NoError(t, txn.SetEntry(NewEntry(kc, []byte("c1")))) |
| require.NoError(t, txn.Commit()) |
| require.Equal(t, uint64(1), db.orc.readTs()) |
| |
| // a2, c2 |
| txn = db.NewTransaction(true) |
| require.NoError(t, txn.SetEntry(NewEntry(ka, []byte("a2")))) |
| require.NoError(t, txn.SetEntry(NewEntry(kc, []byte("c2")))) |
| require.NoError(t, txn.Commit()) |
| require.Equal(t, uint64(2), db.orc.readTs()) |
| |
| // b3 |
| txn = db.NewTransaction(true) |
| require.NoError(t, txn.SetEntry(NewEntry(ka, []byte("a3")))) |
| require.NoError(t, txn.SetEntry(NewEntry(kb, []byte("b3")))) |
| require.NoError(t, txn.Commit()) |
| require.Equal(t, uint64(3), db.orc.readTs()) |
| |
| // b4, c4(del) (Uncommitted) |
| txn4 := db.NewTransaction(true) |
| require.NoError(t, txn4.SetEntry(NewEntry(kb, []byte("b4")))) |
| require.NoError(t, txn4.Delete(kc)) |
| require.Equal(t, uint64(3), db.orc.readTs()) |
| |
| // b4 (del) |
| txn = db.NewTransaction(true) |
| txn.Delete(kb) |
| require.NoError(t, txn.Commit()) |
| require.Equal(t, uint64(4), db.orc.readTs()) |
| |
| checkIterator := func(itr *Iterator, expected []string) { |
| defer itr.Close() |
| var i int |
| for itr.Rewind(); itr.Valid(); itr.Next() { |
| item := itr.Item() |
| val, err := item.ValueCopy(nil) |
| require.NoError(t, err) |
| require.Equal(t, expected[i], string(val), "readts=%d", itr.readTs) |
| i++ |
| } |
| require.Equal(t, len(expected), i) |
| } |
| |
| txn = db.NewTransaction(true) |
| defer txn.Discard() |
| itr := txn.NewIterator(DefaultIteratorOptions) |
| itr5 := txn4.NewIterator(DefaultIteratorOptions) |
| checkIterator(itr, []string{"a3", "c2"}) |
| checkIterator(itr5, []string{"a3", "b4"}) |
| |
| rev := DefaultIteratorOptions |
| rev.Reverse = true |
| itr = txn.NewIterator(rev) |
| itr5 = txn4.NewIterator(rev) |
| checkIterator(itr, []string{"c2", "a3"}) |
| checkIterator(itr5, []string{"b4", "a3"}) |
| |
| txn.readTs = 3 |
| itr = txn.NewIterator(DefaultIteratorOptions) |
| checkIterator(itr, []string{"a3", "b3", "c2"}) |
| itr = txn.NewIterator(rev) |
| checkIterator(itr, []string{"c2", "b3", "a3"}) |
| |
| txn.readTs = 2 |
| itr = txn.NewIterator(DefaultIteratorOptions) |
| checkIterator(itr, []string{"a2", "c2"}) |
| itr = txn.NewIterator(rev) |
| checkIterator(itr, []string{"c2", "a2"}) |
| |
| txn.readTs = 1 |
| itr = txn.NewIterator(DefaultIteratorOptions) |
| checkIterator(itr, []string{"c1"}) |
| itr = txn.NewIterator(rev) |
| checkIterator(itr, []string{"c1"}) |
| }) |
| } |
| |
| // a2, a3, b4 (del), b3, c2, c1 |
| // Read at ts=4 -> a3, c2 |
| // Read at ts=3 -> a3, b3, c2 |
| // Read at ts=2 -> a2, c2 |
| // Read at ts=1 -> c1 |
| func TestTxnIterationEdgeCase2(t *testing.T) { |
| runBadgerTest(t, nil, func(t *testing.T, db *DB) { |
| ka := []byte("a") |
| kb := []byte("aa") |
| kc := []byte("aaa") |
| |
| // c1 |
| txn := db.NewTransaction(true) |
| require.NoError(t, txn.SetEntry(NewEntry(kc, []byte("c1")))) |
| require.NoError(t, txn.Commit()) |
| require.Equal(t, uint64(1), db.orc.readTs()) |
| |
| // a2, c2 |
| txn = db.NewTransaction(true) |
| require.NoError(t, txn.SetEntry(NewEntry(ka, []byte("a2")))) |
| require.NoError(t, txn.SetEntry(NewEntry(kc, []byte("c2")))) |
| require.NoError(t, txn.Commit()) |
| require.Equal(t, uint64(2), db.orc.readTs()) |
| |
| // b3 |
| txn = db.NewTransaction(true) |
| require.NoError(t, txn.SetEntry(NewEntry(ka, []byte("a3")))) |
| require.NoError(t, txn.SetEntry(NewEntry(kb, []byte("b3")))) |
| require.NoError(t, txn.Commit()) |
| require.Equal(t, uint64(3), db.orc.readTs()) |
| |
| // b4 (del) |
| txn = db.NewTransaction(true) |
| txn.Delete(kb) |
| require.NoError(t, txn.Commit()) |
| require.Equal(t, uint64(4), db.orc.readTs()) |
| |
| checkIterator := func(itr *Iterator, expected []string) { |
| defer itr.Close() |
| var i int |
| for itr.Rewind(); itr.Valid(); itr.Next() { |
| item := itr.Item() |
| val, err := item.ValueCopy(nil) |
| require.NoError(t, err) |
| require.Equal(t, expected[i], string(val), "readts=%d", itr.readTs) |
| i++ |
| } |
| require.Equal(t, len(expected), i) |
| } |
| txn = db.NewTransaction(true) |
| defer txn.Discard() |
| rev := DefaultIteratorOptions |
| rev.Reverse = true |
| |
| itr := txn.NewIterator(DefaultIteratorOptions) |
| checkIterator(itr, []string{"a3", "c2"}) |
| itr = txn.NewIterator(rev) |
| checkIterator(itr, []string{"c2", "a3"}) |
| |
| txn.readTs = 5 |
| itr = txn.NewIterator(DefaultIteratorOptions) |
| itr.Seek(ka) |
| require.True(t, itr.Valid()) |
| require.Equal(t, itr.item.Key(), ka) |
| itr.Seek(kc) |
| require.True(t, itr.Valid()) |
| require.Equal(t, itr.item.Key(), kc) |
| itr.Close() |
| |
| itr = txn.NewIterator(rev) |
| itr.Seek(ka) |
| require.True(t, itr.Valid()) |
| require.Equal(t, itr.item.Key(), ka) |
| itr.Seek(kc) |
| require.True(t, itr.Valid()) |
| require.Equal(t, itr.item.Key(), kc) |
| itr.Close() |
| |
| txn.readTs = 3 |
| itr = txn.NewIterator(DefaultIteratorOptions) |
| checkIterator(itr, []string{"a3", "b3", "c2"}) |
| itr = txn.NewIterator(rev) |
| checkIterator(itr, []string{"c2", "b3", "a3"}) |
| |
| txn.readTs = 2 |
| itr = txn.NewIterator(DefaultIteratorOptions) |
| checkIterator(itr, []string{"a2", "c2"}) |
| itr = txn.NewIterator(rev) |
| checkIterator(itr, []string{"c2", "a2"}) |
| |
| txn.readTs = 1 |
| itr = txn.NewIterator(DefaultIteratorOptions) |
| checkIterator(itr, []string{"c1"}) |
| itr = txn.NewIterator(rev) |
| checkIterator(itr, []string{"c1"}) |
| }) |
| } |
| |
| func TestTxnIterationEdgeCase3(t *testing.T) { |
| runBadgerTest(t, nil, func(t *testing.T, db *DB) { |
| kb := []byte("abc") |
| kc := []byte("acd") |
| kd := []byte("ade") |
| |
| // c1 |
| txn := db.NewTransaction(true) |
| require.NoError(t, txn.SetEntry(NewEntry(kc, []byte("c1")))) |
| require.NoError(t, txn.Commit()) |
| require.Equal(t, uint64(1), db.orc.readTs()) |
| |
| // b2 |
| txn = db.NewTransaction(true) |
| require.NoError(t, txn.SetEntry(NewEntry(kb, []byte("b2")))) |
| require.NoError(t, txn.Commit()) |
| require.Equal(t, uint64(2), db.orc.readTs()) |
| |
| txn2 := db.NewTransaction(true) |
| require.NoError(t, txn2.SetEntry(NewEntry(kd, []byte("d2")))) |
| require.NoError(t, txn2.Delete(kc)) |
| |
| txn = db.NewTransaction(true) |
| defer txn.Discard() |
| rev := DefaultIteratorOptions |
| rev.Reverse = true |
| |
| itr := txn.NewIterator(DefaultIteratorOptions) |
| itr.Seek([]byte("ab")) |
| require.True(t, itr.Valid()) |
| require.Equal(t, itr.item.Key(), kb) |
| itr.Seek([]byte("ac")) |
| require.True(t, itr.Valid()) |
| require.Equal(t, itr.item.Key(), kc) |
| itr.Seek(nil) |
| require.True(t, itr.Valid()) |
| require.Equal(t, itr.item.Key(), kb) |
| itr.Seek([]byte("ac")) |
| itr.Rewind() |
| itr.Seek(nil) |
| require.True(t, itr.Valid()) |
| require.Equal(t, itr.item.Key(), kb) |
| itr.Seek([]byte("ac")) |
| require.True(t, itr.Valid()) |
| require.Equal(t, itr.item.Key(), kc) |
| itr.Close() |
| |
| // Keys: "abc", "ade" |
| // Read pending writes. |
| itr = txn2.NewIterator(DefaultIteratorOptions) |
| itr.Seek([]byte("ab")) |
| require.True(t, itr.Valid()) |
| require.Equal(t, itr.item.Key(), kb) |
| itr.Seek([]byte("ac")) |
| require.True(t, itr.Valid()) |
| require.Equal(t, itr.item.Key(), kd) |
| itr.Seek(nil) |
| require.True(t, itr.Valid()) |
| require.Equal(t, itr.item.Key(), kb) |
| itr.Seek([]byte("ac")) |
| itr.Rewind() |
| itr.Seek(nil) |
| require.True(t, itr.Valid()) |
| require.Equal(t, itr.item.Key(), kb) |
| itr.Seek([]byte("ad")) |
| require.True(t, itr.Valid()) |
| require.Equal(t, itr.item.Key(), kd) |
| itr.Close() |
| |
| itr = txn.NewIterator(rev) |
| itr.Seek([]byte("ac")) |
| require.True(t, itr.Valid()) |
| require.Equal(t, itr.item.Key(), kb) |
| itr.Seek([]byte("ad")) |
| require.True(t, itr.Valid()) |
| require.Equal(t, itr.item.Key(), kc) |
| itr.Seek(nil) |
| require.True(t, itr.Valid()) |
| require.Equal(t, itr.item.Key(), kc) |
| itr.Seek([]byte("ac")) |
| itr.Rewind() |
| require.True(t, itr.Valid()) |
| require.Equal(t, itr.item.Key(), kc) |
| itr.Seek([]byte("ad")) |
| require.True(t, itr.Valid()) |
| require.Equal(t, itr.item.Key(), kc) |
| itr.Close() |
| |
| // Keys: "abc", "ade" |
| itr = txn2.NewIterator(rev) |
| itr.Seek([]byte("ad")) |
| require.True(t, itr.Valid()) |
| require.Equal(t, itr.item.Key(), kb) |
| itr.Seek([]byte("ae")) |
| require.True(t, itr.Valid()) |
| require.Equal(t, itr.item.Key(), kd) |
| itr.Seek(nil) |
| require.True(t, itr.Valid()) |
| require.Equal(t, itr.item.Key(), kd) |
| itr.Seek([]byte("ab")) |
| itr.Rewind() |
| require.True(t, itr.Valid()) |
| require.Equal(t, itr.item.Key(), kd) |
| itr.Seek([]byte("ac")) |
| require.True(t, itr.Valid()) |
| require.Equal(t, itr.item.Key(), kb) |
| itr.Close() |
| }) |
| } |
| |
| func TestIteratorAllVersionsWithDeleted(t *testing.T) { |
| test := func(t *testing.T, db *DB) { |
| // Write two keys |
| err := db.Update(func(txn *Txn) error { |
| require.NoError(t, txn.SetEntry(NewEntry([]byte("answer1"), []byte("42")))) |
| return txn.SetEntry(NewEntry([]byte("answer2"), []byte("43"))) |
| }) |
| require.NoError(t, err) |
| |
| // Delete the specific key version from underlying db directly |
| err = db.View(func(txn *Txn) error { |
| item, err := txn.Get([]byte("answer1")) |
| require.NoError(t, err) |
| err = txn.db.batchSet([]*Entry{ |
| { |
| Key: y.KeyWithTs(item.key, item.version), |
| meta: bitDelete, |
| }, |
| }) |
| require.NoError(t, err) |
| return err |
| }) |
| require.NoError(t, err) |
| |
| opts := DefaultIteratorOptions |
| opts.AllVersions = true |
| opts.PrefetchValues = false |
| |
| // Verify that deleted shows up when AllVersions is set. |
| err = db.View(func(txn *Txn) error { |
| it := txn.NewIterator(opts) |
| defer it.Close() |
| var count int |
| for it.Rewind(); it.Valid(); it.Next() { |
| count++ |
| item := it.Item() |
| if count == 1 { |
| require.Equal(t, []byte("answer1"), item.Key()) |
| require.True(t, item.meta&bitDelete > 0) |
| } else { |
| require.Equal(t, []byte("answer2"), item.Key()) |
| } |
| } |
| require.Equal(t, 2, count) |
| return nil |
| }) |
| require.NoError(t, err) |
| } |
| t.Run("disk mode", func(t *testing.T) { |
| runBadgerTest(t, nil, func(t *testing.T, db *DB) { |
| test(t, db) |
| }) |
| }) |
| t.Run("InMemory mode", func(t *testing.T) { |
| opt := getTestOptions("") |
| opt.InMemory = true |
| db, err := Open(opt) |
| require.NoError(t, err) |
| test(t, db) |
| require.NoError(t, db.Close()) |
| }) |
| } |
| |
| func TestIteratorAllVersionsWithDeleted2(t *testing.T) { |
| runBadgerTest(t, nil, func(t *testing.T, db *DB) { |
| // Set and delete alternatively |
| for i := 0; i < 4; i++ { |
| err := db.Update(func(txn *Txn) error { |
| if i%2 == 0 { |
| require.NoError(t, txn.SetEntry(NewEntry([]byte("key"), []byte("value")))) |
| return nil |
| } |
| return txn.Delete([]byte("key")) |
| }) |
| require.NoError(t, err) |
| } |
| |
| opts := DefaultIteratorOptions |
| opts.AllVersions = true |
| opts.PrefetchValues = false |
| |
| // Verify that deleted shows up when AllVersions is set. |
| err := db.View(func(txn *Txn) error { |
| it := txn.NewIterator(opts) |
| defer it.Close() |
| var count int |
| for it.Rewind(); it.Valid(); it.Next() { |
| item := it.Item() |
| require.Equal(t, []byte("key"), item.Key()) |
| if count%2 != 0 { |
| val, err := item.ValueCopy(nil) |
| require.NoError(t, err) |
| require.Equal(t, val, []byte("value")) |
| } else { |
| require.True(t, item.meta&bitDelete > 0) |
| } |
| count++ |
| } |
| require.Equal(t, 4, count) |
| return nil |
| }) |
| require.NoError(t, err) |
| }) |
| } |
| |
| func TestManagedDB(t *testing.T) { |
| dir, err := ioutil.TempDir("", "badger-test") |
| require.NoError(t, err) |
| defer removeDir(dir) |
| |
| opt := getTestOptions(dir) |
| opt.managedTxns = true |
| |
| test := func(t *testing.T, db *DB) { |
| key := func(i int) []byte { |
| return []byte(fmt.Sprintf("key-%02d", i)) |
| } |
| |
| val := func(i int) []byte { |
| return []byte(fmt.Sprintf("val-%d", i)) |
| } |
| |
| require.Panics(t, func() { |
| db.Update(func(tx *Txn) error { return nil }) |
| }) |
| |
| err = db.View(func(tx *Txn) error { return nil }) |
| require.NoError(t, err) |
| |
| // Write data at t=3. |
| txn := db.NewTransactionAt(3, true) |
| for i := 0; i <= 3; i++ { |
| require.NoError(t, txn.SetEntry(NewEntry(key(i), val(i)))) |
| } |
| require.Panics(t, func() { txn.Commit() }) |
| require.NoError(t, txn.CommitAt(3, nil)) |
| |
| // Read data at t=2. |
| txn = db.NewTransactionAt(2, false) |
| for i := 0; i <= 3; i++ { |
| _, err := txn.Get(key(i)) |
| require.Equal(t, ErrKeyNotFound, err) |
| } |
| txn.Discard() |
| |
| // Read data at t=3. |
| txn = db.NewTransactionAt(3, false) |
| for i := 0; i <= 3; i++ { |
| item, err := txn.Get(key(i)) |
| require.NoError(t, err) |
| require.Equal(t, uint64(3), item.Version()) |
| v, err := item.ValueCopy(nil) |
| require.NoError(t, err) |
| require.Equal(t, val(i), v) |
| } |
| txn.Discard() |
| |
| // Write data at t=7. |
| txn = db.NewTransactionAt(6, true) |
| for i := 0; i <= 7; i++ { |
| _, err := txn.Get(key(i)) |
| if err == nil { |
| continue // Don't overwrite existing keys. |
| } |
| require.NoError(t, txn.SetEntry(NewEntry(key(i), val(i)))) |
| } |
| require.NoError(t, txn.CommitAt(7, nil)) |
| |
| // Read data at t=9. |
| txn = db.NewTransactionAt(9, false) |
| for i := 0; i <= 9; i++ { |
| item, err := txn.Get(key(i)) |
| if i <= 7 { |
| require.NoError(t, err) |
| } else { |
| require.Equal(t, ErrKeyNotFound, err) |
| } |
| |
| if i <= 3 { |
| require.Equal(t, uint64(3), item.Version()) |
| } else if i <= 7 { |
| require.Equal(t, uint64(7), item.Version()) |
| } |
| if i <= 7 { |
| v, err := item.ValueCopy(nil) |
| require.NoError(t, err) |
| require.Equal(t, val(i), v) |
| } |
| } |
| txn.Discard() |
| } |
| t.Run("disk mode", func(t *testing.T) { |
| db, err := Open(opt) |
| require.NoError(t, err) |
| test(t, db) |
| require.NoError(t, db.Close()) |
| }) |
| t.Run("InMemory mode", func(t *testing.T) { |
| opt.InMemory = true |
| opt.Dir = "" |
| opt.ValueDir = "" |
| db, err := Open(opt) |
| require.NoError(t, err) |
| test(t, db) |
| require.NoError(t, db.Close()) |
| }) |
| |
| } |
| |
| func TestArmV7Issue311Fix(t *testing.T) { |
| dir, err := ioutil.TempDir("", "") |
| if err != nil { |
| t.Fatal(err) |
| } |
| defer removeDir(dir) |
| |
| db, err := Open(DefaultOptions(dir). |
| WithTableLoadingMode(options.MemoryMap). |
| WithValueLogFileSize(16 << 20). |
| WithLevelOneSize(8 << 20). |
| WithMaxTableSize(2 << 20). |
| WithSyncWrites(false)) |
| if err != nil { |
| t.Fatalf("cannot open db at location %s: %v", dir, err) |
| } |
| |
| err = db.View(func(txn *Txn) error { return nil }) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| err = db.Update(func(txn *Txn) error { |
| return txn.SetEntry(NewEntry([]byte{0x11}, []byte{0x22})) |
| }) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| err = db.Update(func(txn *Txn) error { |
| return txn.SetEntry(NewEntry([]byte{0x11}, []byte{0x22})) |
| }) |
| |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| if err = db.Close(); err != nil { |
| t.Fatal(err) |
| } |
| } |