Files
zapdb/merge_test.go
zeekayandHanzo Dev fe8d85d663 rename package badger -> zapdb
The module has been github.com/luxfi/zapdb for a while, but the package inside
it still declared itself badger, so every consumer had to write

	zapdb "github.com/luxfi/zapdb" // package badger

and the alias existed only to paper over the mismatch. 56 root files and the
134 qualified references inside the repo now say zapdb.

Backward compatible in both directions: an existing consumer with the alias
keeps compiling (the alias is now merely redundant), and one without it starts
compiling instead of failing on an undefined zapdb.

Directory badger/ and its cmd package are untouched — that is the CLI, and its
binary is named badger.

Build and vet clean; suite green except TestZRestore, which wants a /tmp/zb/src
benchmark fixture and fails identically without this change.

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-27 14:24:18 -07:00

179 lines
4.7 KiB
Go

/*
* SPDX-FileCopyrightText: © 2017-2025 Istari Digital, Inc.
* SPDX-License-Identifier: Apache-2.0
*/
package zapdb
import (
"encoding/binary"
"os"
"testing"
"time"
"github.com/stretchr/testify/require"
)
func TestGetMergeOperator(t *testing.T) {
t.Run("Get before Add", func(t *testing.T) {
runBadgerTest(t, nil, func(t *testing.T, db *DB) {
m := db.GetMergeOperator([]byte("merge"), add, 200*time.Millisecond)
defer m.Stop()
val, err := m.Get()
require.Equal(t, ErrKeyNotFound, err)
require.Nil(t, val)
})
})
t.Run("Add and Get", func(t *testing.T) {
key := []byte("merge")
runBadgerTest(t, nil, func(t *testing.T, db *DB) {
m := db.GetMergeOperator(key, add, 200*time.Millisecond)
defer m.Stop()
require.NoError(t, m.Add(uint64ToBytes(1)))
require.NoError(t, m.Add(uint64ToBytes(2)))
require.NoError(t, m.Add(uint64ToBytes(3)))
res, err := m.Get()
require.NoError(t, err)
require.Equal(t, uint64(6), bytesToUint64(res))
})
})
t.Run("Add and Get slices", func(t *testing.T) {
// Merge function to merge two byte slices
add := func(originalValue, newValue []byte) []byte {
return append(originalValue, newValue...)
}
runBadgerTest(t, nil, func(t *testing.T, db *DB) {
m := db.GetMergeOperator([]byte("fooprefix"), add, 2*time.Millisecond)
defer m.Stop()
require.NoError(t, m.Add([]byte("A")))
require.NoError(t, m.Add([]byte("B")))
require.NoError(t, m.Add([]byte("C")))
value, err := m.Get()
require.NoError(t, err)
require.Equal(t, "ABC", string(value))
})
})
t.Run("Get Before Compact", func(t *testing.T) {
key := []byte("merge")
runBadgerTest(t, nil, func(t *testing.T, db *DB) {
m := db.GetMergeOperator(key, add, 500*time.Millisecond)
defer m.Stop()
require.NoError(t, m.Add(uint64ToBytes(1)))
require.NoError(t, m.Add(uint64ToBytes(2)))
require.NoError(t, m.Add(uint64ToBytes(3)))
res, err := m.Get()
require.NoError(t, err)
require.Equal(t, uint64(6), bytesToUint64(res))
})
})
t.Run("Get after Delete", func(t *testing.T) {
key := []byte("merge")
runBadgerTest(t, nil, func(t *testing.T, db *DB) {
m := db.GetMergeOperator(key, add, 200*time.Millisecond)
require.NoError(t, m.Add(uint64ToBytes(1)))
require.NoError(t, m.Add(uint64ToBytes(2)))
require.NoError(t, m.Add(uint64ToBytes(3)))
m.Stop()
res, err := m.Get()
require.NoError(t, err)
require.Equal(t, uint64(6), bytesToUint64(res))
require.NoError(t, db.Update(func(txn *Txn) error {
return txn.Delete(key)
}))
m = db.GetMergeOperator(key, add, 200*time.Millisecond)
require.NoError(t, m.Add(uint64ToBytes(1)))
m.Stop()
res, err = m.Get()
require.NoError(t, err)
require.Equal(t, uint64(1), bytesToUint64(res))
})
})
t.Run("Get after Stop", func(t *testing.T) {
key := []byte("merge")
runBadgerTest(t, nil, func(t *testing.T, db *DB) {
m := db.GetMergeOperator(key, add, 1*time.Second)
require.NoError(t, m.Add(uint64ToBytes(1)))
require.NoError(t, m.Add(uint64ToBytes(2)))
require.NoError(t, m.Add(uint64ToBytes(3)))
m.Stop()
res, err := m.Get()
require.NoError(t, err)
require.Equal(t, uint64(6), bytesToUint64(res))
})
})
t.Run("Old keys should be removed after compaction", func(t *testing.T) {
dir, err := os.MkdirTemp("", "badger-test")
require.NoError(t, err)
defer removeDir(dir)
// This test relies on CompactL0OnClose
opts := getTestOptions(dir).WithCompactL0OnClose(true)
db, err := Open(opts)
require.NoError(t, err)
mergeKey := []byte("foo")
m := db.GetMergeOperator(mergeKey, add, 2*time.Millisecond)
count := 5000 // This will cause compaction from L0->L1
for i := 0; i < count; i++ {
require.NoError(t, m.Add(uint64ToBytes(1)))
}
value, err := m.Get()
require.Nil(t, err)
require.Equal(t, uint64(count), bytesToUint64(value))
m.Stop()
// Force compaction by closing DB. The compaction should discard all the old merged values
require.Nil(t, db.Close())
db, err = Open(opts)
require.NoError(t, err)
defer db.Close()
keyCount := 0
txn := db.NewTransaction(false)
defer txn.Discard()
iopt := DefaultIteratorOptions
iopt.AllVersions = true
it := txn.NewKeyIterator(mergeKey, iopt)
defer it.Close()
for it.Rewind(); it.Valid(); it.Next() {
keyCount++
}
// We should have only one key in badger. All the other keys should've been removed by
// compaction
require.Equal(t, 1, keyCount)
})
}
func uint64ToBytes(i uint64) []byte {
var buf [8]byte
binary.BigEndian.PutUint64(buf[:], i)
return buf[:]
}
func bytesToUint64(b []byte) uint64 {
return binary.BigEndian.Uint64(b)
}
// Merge function to add two uint64 numbers
func add(existing, latest []byte) []byte {
return uint64ToBytes(bytesToUint64(existing) + bytesToUint64(latest))
}