blob: 432d4b498692f0cdd813cfb69a544ffacd0b890f [file] [log] [blame]
/*
* 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 (
"bytes"
"fmt"
"io/ioutil"
"log"
"math"
"math/rand"
"os"
"regexp"
"sort"
"sync"
"testing"
"github.com/dgraph-io/badger/y"
"github.com/stretchr/testify/require"
)
func getTestOptions(dir string) Options {
opt := DefaultOptions
opt.MaxTableSize = 1 << 15 // Force more compaction.
opt.LevelOneSize = 4 << 15 // Force more compaction.
opt.Dir = dir
opt.ValueDir = dir
opt.SyncWrites = false
return opt
}
func getItemValue(t *testing.T, item *Item) (val []byte) {
v, err := item.Value()
if err != nil {
t.Error(err)
}
if v == nil {
return nil
}
return v
}
func txnSet(t *testing.T, kv *DB, key []byte, val []byte, meta byte) {
txn := kv.NewTransaction(true)
require.NoError(t, txn.Set(key, val, meta))
require.NoError(t, txn.Commit(nil))
}
func txnDelete(t *testing.T, kv *DB, key []byte) {
txn := kv.NewTransaction(true)
require.NoError(t, txn.Delete(key))
require.NoError(t, txn.Commit(nil))
}
func TestWrite(t *testing.T) {
dir, err := ioutil.TempDir("", "badger")
require.NoError(t, err)
defer os.RemoveAll(dir)
kv, err := Open(getTestOptions(dir))
require.NoError(t, err)
defer kv.Close()
for i := 0; i < 100; i++ {
txnSet(t, kv, []byte(fmt.Sprintf("key%d", i)), []byte(fmt.Sprintf("val%d", i)), 0x00)
}
}
func TestUpdateAndView(t *testing.T) {
dir, err := ioutil.TempDir("", "badger")
require.NoError(t, err)
defer os.RemoveAll(dir)
db, err := Open(getTestOptions(dir))
require.NoError(t, err)
defer db.Close()
err = db.Update(func(txn *Txn) error {
for i := 0; i < 10; i++ {
err := txn.Set([]byte(fmt.Sprintf("key%d", i)), []byte(fmt.Sprintf("val%d", i)), 0x00)
if err != nil {
return err
}
}
return nil
})
require.NoError(t, err)
err = db.View(func(txn *Txn) error {
for i := 0; i < 10; i++ {
item, err := txn.Get([]byte(fmt.Sprintf("key%d", i)))
if err != nil {
return err
}
val, err := item.Value()
if err != nil {
return err
}
expected := []byte(fmt.Sprintf("val%d", i))
require.Equal(t, expected, val,
"Invalid value for key %q. expected: %q, actual: %q",
item.Key(), expected, val)
}
return nil
})
require.NoError(t, err)
}
func TestConcurrentWrite(t *testing.T) {
dir, err := ioutil.TempDir("", "badger")
require.NoError(t, err)
defer os.RemoveAll(dir)
kv, _ := Open(getTestOptions(dir))
defer kv.Close()
// Not a benchmark. Just a simple test for concurrent writes.
n := 20
m := 500
var wg sync.WaitGroup
for i := 0; i < n; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
for j := 0; j < m; j++ {
txnSet(t, kv, []byte(fmt.Sprintf("k%05d_%08d", i, j)),
[]byte(fmt.Sprintf("v%05d_%08d", i, j)), byte(j%127))
}
}(i)
}
wg.Wait()
t.Log("Starting iteration")
opt := IteratorOptions{}
opt.Reverse = false
opt.PrefetchSize = 10
opt.PrefetchValues = true
txn := kv.NewTransaction(true)
it := txn.NewIterator(opt)
defer it.Close()
var i, j int
for it.Rewind(); it.Valid(); it.Next() {
item := it.Item()
k := item.Key()
if k == nil {
break // end of iteration.
}
require.EqualValues(t, fmt.Sprintf("k%05d_%08d", i, j), string(k))
v := getItemValue(t, item)
require.EqualValues(t, fmt.Sprintf("v%05d_%08d", i, j), string(v))
require.Equal(t, item.UserMeta(), byte(j%127))
j++
if j == m {
i++
j = 0
}
}
require.EqualValues(t, n, i)
require.EqualValues(t, 0, j)
}
func TestGet(t *testing.T) {
dir, err := ioutil.TempDir("", "badger")
require.NoError(t, err)
defer os.RemoveAll(dir)
kv, err := Open(getTestOptions(dir))
if err != nil {
t.Error(err)
}
defer kv.Close()
txnSet(t, kv, []byte("key1"), []byte("val1"), 0x08)
txn := kv.NewTransaction(false)
item, err := txn.Get([]byte("key1"))
require.NoError(t, err)
require.EqualValues(t, "val1", getItemValue(t, item))
require.Equal(t, byte(0x08), item.UserMeta())
txn.Discard()
txnSet(t, kv, []byte("key1"), []byte("val2"), 0x09)
txn = kv.NewTransaction(false)
item, err = txn.Get([]byte("key1"))
require.NoError(t, err)
require.EqualValues(t, "val2", getItemValue(t, item))
require.Equal(t, byte(0x09), item.UserMeta())
txn.Discard()
txnDelete(t, kv, []byte("key1"))
txn = kv.NewTransaction(false)
_, err = txn.Get([]byte("key1"))
require.Equal(t, ErrKeyNotFound, err)
txn.Discard()
txnSet(t, kv, []byte("key1"), []byte("val3"), 0x01)
txn = kv.NewTransaction(false)
item, err = txn.Get([]byte("key1"))
require.NoError(t, err)
require.EqualValues(t, "val3", getItemValue(t, item))
require.Equal(t, byte(0x01), item.UserMeta())
longVal := make([]byte, 1000)
txnSet(t, kv, []byte("key1"), longVal, 0x00)
txn = kv.NewTransaction(false)
item, err = txn.Get([]byte("key1"))
require.NoError(t, err)
require.EqualValues(t, longVal, getItemValue(t, item))
txn.Discard()
}
func TestExists(t *testing.T) {
dir, err := ioutil.TempDir("", "badger")
require.NoError(t, err)
defer os.RemoveAll(dir)
kv, err := Open(getTestOptions(dir))
if err != nil {
t.Error(err)
}
defer kv.Close()
// populate with one entry
txnSet(t, kv, []byte("key1"), []byte("val1"), 0x00)
tt := []struct {
key []byte
exists bool
}{
{
key: []byte("key1"),
exists: true,
},
{
key: []byte("non-exits"),
exists: false,
},
}
for _, test := range tt {
require.NoError(t, kv.View(func(tx *Txn) error {
_, err := tx.Get(test.key)
if test.exists {
require.NoError(t, err)
return nil
}
require.Equal(t, ErrKeyNotFound, err)
return nil
}))
}
}
// Put a lot of data to move some data to disk.
// WARNING: This test might take a while but it should pass!
func TestGetMore(t *testing.T) {
dir, err := ioutil.TempDir("", "badger")
require.NoError(t, err)
defer os.RemoveAll(dir)
kv, err := Open(getTestOptions(dir))
if err != nil {
t.Error(err)
t.Fail()
}
defer kv.Close()
data := func(i int) []byte {
return []byte(fmt.Sprintf("%b", i))
}
// n := 500000
n := 10000
m := 49 // Increasing would cause ErrTxnTooBig
for i := 0; i < n; i += m {
txn := kv.NewTransaction(true)
for j := i; j < i+m && j < n; j++ {
require.NoError(t, txn.Set(data(j), data(j), 0))
}
require.NoError(t, txn.Commit(nil))
}
require.NoError(t, kv.validate())
for i := 0; i < n; i++ {
txn := kv.NewTransaction(false)
item, err := txn.Get(data(i))
if err != nil {
t.Error(err)
}
require.EqualValues(t, string(data(i)), string(getItemValue(t, item)))
txn.Discard()
}
// Overwrite
for i := 0; i < n; i += m {
txn := kv.NewTransaction(true)
for j := i; j < i+m && j < n; j++ {
require.NoError(t, txn.Set(data(j),
// Use a long value that will certainly exceed value threshold.
[]byte(fmt.Sprintf("zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz%9d", j)),
0x00))
}
require.NoError(t, txn.Commit(nil))
}
require.NoError(t, kv.validate())
for i := 0; i < n; i++ {
expectedValue := fmt.Sprintf("zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz%9d", i)
k := data(i)
txn := kv.NewTransaction(false)
item, err := txn.Get(k)
if err != nil {
t.Error(err)
}
got := string(getItemValue(t, item))
if expectedValue != got {
vs, err := kv.get(y.KeyWithTs(k, math.MaxUint64))
require.NoError(t, err)
fmt.Printf("wanted=%q Item: %s\n", k, item.ToString())
fmt.Printf("on re-run, got version: %+v\n", vs)
txn := kv.NewTransaction(false)
itr := txn.NewIterator(DefaultIteratorOptions)
for itr.Seek(k); itr.Valid(); itr.Next() {
item := itr.Item()
fmt.Printf("item=%s\n", item.ToString())
if !bytes.Equal(item.Key(), k) {
break
}
}
itr.Close()
txn.Discard()
}
require.EqualValues(t, expectedValue, string(getItemValue(t, item)), "wanted=%q Item: %s\n", k, item.ToString())
txn.Discard()
}
// "Delete" key.
for i := 0; i < n; i += m {
if (i % 10000) == 0 {
fmt.Printf("Deleting i=%d\n", i)
}
txn := kv.NewTransaction(true)
for j := i; j < i+m && j < n; j++ {
require.NoError(t, txn.Delete(data(j)))
}
require.NoError(t, txn.Commit(nil))
}
kv.validate()
for i := 0; i < n; i++ {
if (i % 10000) == 0 {
// Display some progress. Right now, it's not very fast with no caching.
fmt.Printf("Testing i=%d\n", i)
}
k := data(i)
txn := kv.NewTransaction(false)
_, err := txn.Get([]byte(k))
require.Equal(t, ErrKeyNotFound, err, "should not have found k: %q", k)
txn.Discard()
}
}
// Put a lot of data to move some data to disk.
// WARNING: This test might take a while but it should pass!
func TestExistsMore(t *testing.T) {
dir, err := ioutil.TempDir("", "badger")
require.NoError(t, err)
defer os.RemoveAll(dir)
kv, err := Open(getTestOptions(dir))
if err != nil {
t.Error(err)
t.Fail()
}
defer kv.Close()
// n := 500000
n := 10000
m := 49
for i := 0; i < n; i += m {
if (i % 1000) == 0 {
t.Logf("Putting i=%d\n", i)
}
txn := kv.NewTransaction(true)
for j := i; j < i+m && j < n; j++ {
require.NoError(t, txn.Set([]byte(fmt.Sprintf("%09d", j)),
[]byte(fmt.Sprintf("%09d", j)),
0x00))
}
require.NoError(t, txn.Commit(nil))
}
kv.validate()
for i := 0; i < n; i++ {
if (i % 1000) == 0 {
fmt.Printf("Testing i=%d\n", i)
}
k := fmt.Sprintf("%09d", i)
require.NoError(t, kv.View(func(txn *Txn) error {
_, err := txn.Get([]byte(k))
require.NoError(t, err)
return nil
}))
}
require.NoError(t, kv.View(func(txn *Txn) error {
_, err := txn.Get([]byte("non-exists"))
require.Error(t, err)
return nil
}))
// "Delete" key.
for i := 0; i < n; i += m {
if (i % 1000) == 0 {
fmt.Printf("Deleting i=%d\n", i)
}
txn := kv.NewTransaction(true)
for j := i; j < i+m && j < n; j++ {
require.NoError(t, txn.Delete([]byte(fmt.Sprintf("%09d", j))))
}
require.NoError(t, txn.Commit(nil))
}
kv.validate()
for i := 0; i < n; i++ {
if (i % 10000) == 0 {
// Display some progress. Right now, it's not very fast with no caching.
fmt.Printf("Testing i=%d\n", i)
}
k := fmt.Sprintf("%09d", i)
require.NoError(t, kv.View(func(txn *Txn) error {
_, err := txn.Get([]byte(k))
require.Error(t, err)
return nil
}))
}
fmt.Println("Done and closing")
}
func TestIterate2Basic(t *testing.T) {
dir, err := ioutil.TempDir("", "badger")
require.NoError(t, err)
defer os.RemoveAll(dir)
kv, _ := Open(getTestOptions(dir))
defer kv.Close()
bkey := func(i int) []byte {
return []byte(fmt.Sprintf("%09d", i))
}
bval := func(i int) []byte {
return []byte(fmt.Sprintf("%025d", i))
}
// n := 500000
n := 10000
for i := 0; i < n; i++ {
if (i % 1000) == 0 {
t.Logf("Put i=%d\n", i)
}
txnSet(t, kv, bkey(i), bval(i), byte(i%127))
}
opt := IteratorOptions{}
opt.PrefetchValues = true
opt.PrefetchSize = 10
txn := kv.NewTransaction(false)
it := txn.NewIterator(opt)
{
var count int
rewind := true
t.Log("Starting first basic iteration")
for it.Rewind(); it.Valid(); it.Next() {
item := it.Item()
key := item.Key()
if rewind && count == 5000 {
// Rewind would skip /head/ key, and it.Next() would skip 0.
count = 1
it.Rewind()
t.Log("Rewinding from 5000 to zero.")
rewind = false
continue
}
require.EqualValues(t, bkey(count), string(key))
val := getItemValue(t, item)
require.EqualValues(t, bval(count), string(val))
require.Equal(t, byte(count%127), item.UserMeta())
count++
}
require.EqualValues(t, n, count)
}
{
t.Log("Starting second basic iteration")
idx := 5030
for it.Seek(bkey(idx)); it.Valid(); it.Next() {
item := it.Item()
require.EqualValues(t, bkey(idx), string(item.Key()))
require.EqualValues(t, bval(idx), string(getItemValue(t, item)))
idx++
}
}
it.Close()
}
func TestLoad(t *testing.T) {
dir, err := ioutil.TempDir("", "badger")
fmt.Printf("Writing to dir %s\n", dir)
require.NoError(t, err)
defer os.RemoveAll(dir)
n := 10000
{
kv, _ := Open(getTestOptions(dir))
for i := 0; i < n; i++ {
if (i % 10000) == 0 {
fmt.Printf("Putting i=%d\n", i)
}
k := []byte(fmt.Sprintf("%09d", i))
txnSet(t, kv, k, k, 0x00)
}
kv.Close()
}
kv, err := Open(getTestOptions(dir))
require.NoError(t, err)
require.Equal(t, uint64(10001), kv.orc.readTs())
for i := 0; i < n; i++ {
if (i % 10000) == 0 {
fmt.Printf("Testing i=%d\n", i)
}
k := fmt.Sprintf("%09d", i)
require.NoError(t, kv.View(func(txn *Txn) error {
item, err := txn.Get([]byte(k))
require.NoError(t, err)
require.EqualValues(t, k, string(getItemValue(t, item)))
return nil
}))
}
kv.Close()
summary := kv.lc.getSummary()
// Check that files are garbage collected.
idMap := getIDMap(dir)
for fileID := range idMap {
// Check that name is in summary.filenames.
require.True(t, summary.fileIDs[fileID], "%d", fileID)
}
require.EqualValues(t, len(idMap), len(summary.fileIDs))
var fileIDs []uint64
for k := range summary.fileIDs { // Map to array.
fileIDs = append(fileIDs, k)
}
sort.Slice(fileIDs, func(i, j int) bool { return fileIDs[i] < fileIDs[j] })
fmt.Printf("FileIDs: %v\n", fileIDs)
}
func TestIterateDeleted(t *testing.T) {
dir, err := ioutil.TempDir("", "badger")
require.NoError(t, err)
defer os.RemoveAll(dir)
opt := DefaultOptions
opt.SyncWrites = true
opt.Dir = dir
opt.ValueDir = dir
ps, err := Open(opt)
require.NoError(t, err)
defer ps.Close()
txnSet(t, ps, []byte("Key1"), []byte("Value1"), 0x00)
txnSet(t, ps, []byte("Key2"), []byte("Value2"), 0x00)
iterOpt := DefaultIteratorOptions
iterOpt.PrefetchValues = false
txn := ps.NewTransaction(false)
idxIt := txn.NewIterator(iterOpt)
defer idxIt.Close()
count := 0
txn2 := ps.NewTransaction(true)
prefix := []byte("Key")
for idxIt.Seek(prefix); idxIt.ValidForPrefix(prefix); idxIt.Next() {
key := idxIt.Item().Key()
count++
newKey := make([]byte, len(key))
copy(newKey, key)
require.NoError(t, txn2.Delete(newKey))
}
require.Equal(t, 2, count)
require.NoError(t, txn2.Commit(nil))
for _, prefetch := range [...]bool{true, false} {
t.Run(fmt.Sprintf("Prefetch=%t", prefetch), func(t *testing.T) {
txn := ps.NewTransaction(false)
iterOpt = DefaultIteratorOptions
iterOpt.PrefetchValues = prefetch
idxIt = txn.NewIterator(iterOpt)
var estSize int64
var idxKeys []string
for idxIt.Seek(prefix); idxIt.Valid(); idxIt.Next() {
item := idxIt.Item()
key := item.Key()
estSize += item.EstimatedSize()
if !bytes.HasPrefix(key, prefix) {
break
}
idxKeys = append(idxKeys, string(key))
t.Logf("%+v\n", idxIt.Item())
}
require.Equal(t, 0, len(idxKeys))
require.Equal(t, int64(0), estSize)
})
}
}
func TestDirNotExists(t *testing.T) {
_, err := Open(getTestOptions("not-exists"))
require.Error(t, err)
}
func TestDeleteWithoutSyncWrite(t *testing.T) {
dir, err := ioutil.TempDir("", "badger")
require.NoError(t, err)
defer os.RemoveAll(dir)
opt := DefaultOptions
opt.Dir = dir
opt.ValueDir = dir
kv, err := Open(opt)
if err != nil {
t.Error(err)
t.Fail()
}
key := []byte("k1")
// Set a value with size > value threshold so that its written to value log.
txnSet(t, kv, key, []byte("ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789FOOBARZOGZOG"), 0x00)
txnDelete(t, kv, key)
kv.Close()
// Reopen KV
kv, err = Open(opt)
if err != nil {
t.Error(err)
t.Fail()
}
defer kv.Close()
require.NoError(t, kv.View(func(txn *Txn) error {
_, err := txn.Get(key)
require.Error(t, ErrKeyNotFound, err)
return nil
}))
}
func TestPidFile(t *testing.T) {
dir, err := ioutil.TempDir("", "badger")
require.NoError(t, err)
defer os.RemoveAll(dir)
options := getTestOptions(dir)
kv1, err := Open(options)
require.NoError(t, err)
defer kv1.Close()
_, err = Open(options)
require.Error(t, err)
require.Contains(t, err.Error(), "Another process is using this Badger database")
}
func TestBigKeyValuePairs(t *testing.T) {
dir, err := ioutil.TempDir("", "badger")
require.NoError(t, err)
defer os.RemoveAll(dir)
opt := getTestOptions(dir)
kv, err := Open(opt)
require.NoError(t, err)
bigK := make([]byte, maxKeySize+1)
bigV := make([]byte, opt.ValueLogFileSize+1)
small := make([]byte, 10)
txn := kv.NewTransaction(true)
require.Regexp(t, regexp.MustCompile("Key.*exceeded"), txn.Set(bigK, small, 0))
require.Regexp(t, regexp.MustCompile("Value.*exceeded"), txn.Set(small, bigV, 0))
require.NoError(t, txn.Set(small, small, 0x00))
require.Regexp(t, regexp.MustCompile("Key.*exceeded"), txn.Set(bigK, bigV, 0x00))
require.NoError(t, kv.View(func(txn *Txn) error {
_, err := txn.Get(small)
require.Equal(t, ErrKeyNotFound, err)
return nil
}))
require.NoError(t, kv.Close())
}
func TestIteratorPrefetchSize(t *testing.T) {
dir, err := ioutil.TempDir("", "badger")
require.NoError(t, err)
defer os.RemoveAll(dir)
kv, _ := Open(getTestOptions(dir))
defer kv.Close()
bkey := func(i int) []byte {
return []byte(fmt.Sprintf("%09d", i))
}
bval := func(i int) []byte {
return []byte(fmt.Sprintf("%025d", i))
}
n := 100
for i := 0; i < n; i++ {
// if (i % 10) == 0 {
// t.Logf("Put i=%d\n", i)
// }
txnSet(t, kv, bkey(i), bval(i), byte(i%127))
}
getIteratorCount := func(prefetchSize int) int {
opt := IteratorOptions{}
opt.PrefetchValues = true
opt.PrefetchSize = prefetchSize
var count int
txn := kv.NewTransaction(false)
it := txn.NewIterator(opt)
{
t.Log("Starting first basic iteration")
for it.Rewind(); it.Valid(); it.Next() {
count++
}
require.EqualValues(t, n, count)
}
return count
}
var sizes = []int{-10, 0, 1, 10}
for _, size := range sizes {
c := getIteratorCount(size)
require.Equal(t, 100, c)
}
}
func TestSetIfAbsentAsync(t *testing.T) {
dir, err := ioutil.TempDir("", "badger")
require.NoError(t, err)
defer os.RemoveAll(dir)
kv, _ := Open(getTestOptions(dir))
bkey := func(i int) []byte {
return []byte(fmt.Sprintf("%09d", i))
}
f := func(err error) {}
n := 1000
for i := 0; i < n; i++ {
// if (i % 10) == 0 {
// t.Logf("Put i=%d\n", i)
// }
txn := kv.NewTransaction(true)
_, err = txn.Get(bkey(i))
require.Equal(t, ErrKeyNotFound, err)
require.NoError(t, txn.Set(bkey(i), nil, byte(i%127)))
require.NoError(t, txn.Commit(f))
}
require.NoError(t, kv.Close())
kv, err = Open(getTestOptions(dir))
require.NoError(t, err)
opt := DefaultIteratorOptions
txn := kv.NewTransaction(false)
var count int
it := txn.NewIterator(opt)
{
t.Log("Starting first basic iteration")
for it.Rewind(); it.Valid(); it.Next() {
count++
}
require.EqualValues(t, n, count)
}
require.Equal(t, n, count)
require.NoError(t, kv.Close())
}
func TestGetSetRace(t *testing.T) {
dir, err := ioutil.TempDir("", "badger")
require.NoError(t, err)
defer os.RemoveAll(dir)
kv, _ := Open(getTestOptions(dir))
data := make([]byte, 4096)
_, err = rand.Read(data)
require.NoError(t, err)
var (
numOp = 100
wg sync.WaitGroup
keyCh = make(chan string)
)
// writer
wg.Add(1)
go func() {
defer func() {
wg.Done()
close(keyCh)
}()
for i := 0; i < numOp; i++ {
key := fmt.Sprintf("%d", i)
txnSet(t, kv, []byte(key), data, 0x00)
keyCh <- key
}
}()
// reader
wg.Add(1)
go func() {
defer wg.Done()
for key := range keyCh {
require.NoError(t, kv.View(func(txn *Txn) error {
item, err := txn.Get([]byte(key))
require.NoError(t, err)
_, err = item.Value()
require.NoError(t, err)
return nil
}))
}
}()
wg.Wait()
}
func TestPurgeVersionsBelow(t *testing.T) {
dir, err := ioutil.TempDir("", "badger")
require.NoError(t, err)
defer os.RemoveAll(dir)
db, err := Open(getTestOptions(dir))
require.NoError(t, err)
// Write 4 versions of the same key
for i := 0; i < 4; i++ {
err = db.Update(func(txn *Txn) error {
return txn.Set([]byte("answer"), []byte(fmt.Sprintf("%25d", i)), 0)
})
require.NoError(t, err)
}
opts := DefaultIteratorOptions
opts.AllVersions = true
opts.PrefetchValues = false
// Verify that there are 4 versions, and record 3rd version (2nd from top in iteration)
var ts uint64
db.View(func(txn *Txn) error {
it := txn.NewIterator(opts)
var count int
for it.Rewind(); it.Valid(); it.Next() {
count++
item := it.Item()
if count == 2 {
ts = item.Version()
}
require.Equal(t, []byte("answer"), item.Key())
}
require.Equal(t, 4, count)
return nil
})
// Delete all versions below the 3rd version
err = db.PurgeVersionsBelow([]byte("answer"), ts)
require.NoError(t, err)
require.NotEmpty(t, db.vlog.lfDiscardStats.m)
// Verify that there are only 2 versions left
db.View(func(txn *Txn) error {
it := txn.NewIterator(opts)
var count int
for it.Rewind(); it.Valid(); it.Next() {
count++
item := it.Item()
require.True(t, item.Version() >= ts,
"item version: %d older than ts: %d",
item.Version(), ts)
require.Equal(t, []byte("answer"), item.Key())
}
require.Equal(t, 2, count)
return nil
})
}
func TestPurgeOlderVersions(t *testing.T) {
dir, err := ioutil.TempDir("", "badger")
require.NoError(t, err)
defer os.RemoveAll(dir)
db, err := Open(getTestOptions(dir))
require.NoError(t, err)
// Write two versions of a key
err = db.Update(func(txn *Txn) error {
return txn.Set([]byte("answer"), []byte("42"), 0)
})
require.NoError(t, err)
err = db.Update(func(txn *Txn) error {
return txn.Set([]byte("answer"), []byte("43"), 0)
})
require.NoError(t, err)
opts := DefaultIteratorOptions
opts.AllVersions = true
opts.PrefetchValues = false
// Verify that two versions are found during iteration
err = db.View(func(txn *Txn) error {
it := txn.NewIterator(opts)
var count int
for it.Rewind(); it.Valid(); it.Next() {
count++
item := it.Item()
require.Equal(t, []byte("answer"), item.Key())
}
require.Equal(t, 2, count)
return nil
})
require.NoError(t, err)
// Invoke DeleteOlderVersions() to delete older version
err = db.PurgeOlderVersions()
require.NoError(t, err)
// Verify that only one version is found
err = db.View(func(txn *Txn) error {
it := txn.NewIterator(opts)
var count int
for it.Rewind(); it.Valid(); it.Next() {
count++
item := it.Item()
require.Equal(t, []byte("answer"), item.Key())
val, err := item.Value()
require.NoError(t, err)
t.Logf("Item value is %q", val)
//require.Equal(t, []byte("43"), val)
}
require.Equal(t, 1, count)
return nil
})
require.NoError(t, err)
}
func ExampleOpen() {
dir, err := ioutil.TempDir("", "badger")
if err != nil {
log.Fatal(err)
}
defer os.RemoveAll(dir)
opts := DefaultOptions
opts.Dir = dir
opts.ValueDir = dir
db, err := Open(opts)
if err != nil {
log.Fatal(err)
}
defer db.Close()
err = db.View(func(txn *Txn) error {
_, err := txn.Get([]byte("key"))
// We expect ErrKeyNotFound
fmt.Println(err)
return nil
})
if err != nil {
log.Fatal(err)
}
txn := db.NewTransaction(true) // Read-write txn
err = txn.Set([]byte("key"), []byte("value"), 0)
if err != nil {
log.Fatal(err)
}
err = txn.Commit(nil)
if err != nil {
log.Fatal(err)
}
err = db.View(func(txn *Txn) error {
item, err := txn.Get([]byte("key"))
if err != nil {
return err
}
val, err := item.Value()
if err != nil {
return err
}
fmt.Printf("%s\n", string(val))
return nil
})
if err != nil {
log.Fatal(err)
}
// Output:
// Key not found
// value
}
func ExampleTxn_NewIterator() {
dir, err := ioutil.TempDir("", "badger")
if err != nil {
log.Fatal(err)
}
defer os.RemoveAll(dir)
opts := DefaultOptions
opts.Dir = dir
opts.ValueDir = dir
db, err := Open(opts)
if err != nil {
log.Fatal(err)
}
defer db.Close()
bkey := func(i int) []byte {
return []byte(fmt.Sprintf("%09d", i))
}
bval := func(i int) []byte {
return []byte(fmt.Sprintf("%025d", i))
}
txn := db.NewTransaction(true)
// Fill in 1000 items
n := 1000
for i := 0; i < n; i++ {
err := txn.Set(bkey(i), bval(i), 0)
if err != nil {
log.Fatal(err)
}
}
err = txn.Commit(nil)
if err != nil {
log.Fatal(err)
}
opt := DefaultIteratorOptions
opt.PrefetchSize = 10
// Iterate over 1000 items
var count int
err = db.View(func(txn *Txn) error {
it := txn.NewIterator(opt)
for it.Rewind(); it.Valid(); it.Next() {
count++
}
return nil
})
if err != nil {
log.Fatal(err)
}
fmt.Printf("Counted %d elements", count)
// Output:
// Counted 1000 elements
}