featurebase/rbf/tx_test.go
2022-02-28 12:56:24 -07:00

1241 lines
28 KiB
Go

// Copyright 2021 Molecula Corp. All rights reserved.
package rbf_test
import (
"bytes"
"encoding/binary"
"fmt"
"math/rand"
"os"
"path/filepath"
"strings"
"sync"
"testing"
"time"
"github.com/molecula/featurebase/v3/rbf"
"github.com/molecula/featurebase/v3/roaring"
)
func TestTx_CommitRollback(t *testing.T) {
t.Run("NoReopen", func(t *testing.T) {
db := MustOpenDB(t)
defer MustCloseDB(t, db)
// Create bitmap in transaction but rollback.
tx, err := db.Begin(true)
if err != nil {
t.Fatal(err)
} else if err := tx.CreateBitmap("x"); err != nil {
t.Fatal(err)
}
tx.Rollback()
// Create bitmap in transaction again but commit.
if tx, err := db.Begin(true); err != nil {
t.Fatal(err)
} else if err := tx.CreateBitmap("x"); err != nil {
t.Fatal(err)
} else if err := tx.Commit(); err != nil {
t.Fatal(err)
}
// Create bitmap again but it should fail as it already exists.
if tx, err := db.Begin(true); err != nil {
t.Fatal(err)
} else if err := tx.CreateBitmap("x"); err == nil || err != rbf.ErrBitmapExists {
tx.Rollback()
t.Fatal(err)
} else if err := tx.Commit(); err != nil {
t.Fatal(err)
}
})
t.Run("Reopen", func(t *testing.T) {
db := MustOpenDB(t)
defer func() { MustCloseDB(t, db) }()
// Create bitmap in transaction but rollback.
tx, err := db.Begin(true)
if err != nil {
t.Fatal(err)
} else if err := tx.CreateBitmap("x"); err != nil {
t.Fatal(err)
}
tx.Rollback()
db = MustReopenDB(t, db)
// Create bitmap in transaction again but commit.
if tx, err := db.Begin(true); err != nil {
t.Fatal(err)
} else if err := tx.CreateBitmap("x"); err != nil {
t.Fatal(err)
} else if err := tx.Commit(); err != nil {
t.Fatal(err)
}
db = MustReopenDB(t, db)
// Create bitmap again but it should fail as it already exists.
if tx, err := db.Begin(true); err != nil {
t.Fatal(err)
} else if err := tx.CreateBitmap("x"); err == nil || err != rbf.ErrBitmapExists {
tx.Rollback()
t.Fatal(err)
} else if err := tx.Commit(); err != nil {
t.Fatal(err)
}
})
t.Run("ReopenReadOnly", func(t *testing.T) {
db := MustOpenDB(t)
defer func() { MustCloseDB(t, db) }()
// Create bitmap in transaction and commit.
if tx, err := db.Begin(true); err != nil {
t.Fatal(err)
} else if err := tx.CreateBitmap("x"); err != nil {
t.Fatal(err)
} else if err := tx.Commit(); err != nil {
t.Fatal(err)
}
db = MustReopenDB(t, db)
// Create bitmap again but it should fail as it already exists.
if tx, err := db.Begin(false); err != nil {
t.Fatal(err)
} else if _, err := tx.Count("x"); err != nil {
t.Fatal(err)
} else if err := tx.Commit(); err != nil {
t.Fatal(err)
}
})
t.Run("SingleWriter", func(t *testing.T) {
db := MustOpenDB(t)
defer MustCloseDB(t, db)
var wg sync.WaitGroup
defer wg.Wait()
// Start write transaction.
ch0 := make(chan struct{})
tx0 := MustBegin(t, db, true)
wg.Add(1)
go func() {
defer wg.Done()
<-ch0
tx0.Rollback()
}()
// Start separate write transaction in different goroutine.
ch1 := make(chan struct{})
wg.Add(1)
go func() {
defer wg.Done()
tx1 := MustBegin(t, db, true)
close(ch1)
_ = tx1.Commit()
}()
// Ensure second tx doesn't start.
select {
case <-ch1:
t.Fatal("second tx started while first tx active")
case <-time.After(50 * time.Millisecond):
}
// Finish first transaction.
close(ch0)
select {
case <-ch1:
case <-time.After(10 * time.Second):
t.Fatal("second tx should have started after first tx closed")
}
})
}
func TestTx_Add(t *testing.T) {
db := MustOpenDB(t)
defer MustCloseDB(t, db)
tx := MustBegin(t, db, true)
defer tx.Rollback()
if err := tx.CreateBitmap("x"); err != nil {
t.Fatal(err)
}
if _, err := tx.Add("x", 1); err != nil {
t.Fatal(err)
} else if _, err := tx.Add("x", 10); err != nil {
t.Fatal(err)
} else if _, err := tx.Add("x", 3); err != nil {
t.Fatal(err)
}
for _, v := range []uint64{1, 3, 10} {
if ok, err := tx.Contains("x", v); err != nil {
t.Fatal(err)
} else if !ok {
t.Fatalf("Tx.Contains(%d): expected true", v)
}
}
if ok, err := tx.Contains("x", 2); err != nil {
t.Fatal(err)
} else if ok {
t.Fatal("Tx.Contains(): expected false")
}
}
func TestTx_DeleteBitmap(t *testing.T) {
db := MustOpenDB(t)
defer MustCloseDB(t, db)
tx := MustBegin(t, db, true)
defer tx.Rollback()
// Create bitmap & add value.
if err := tx.CreateBitmap("x"); err != nil {
t.Fatal(err)
} else if _, err := tx.Add("x", 1); err != nil {
t.Fatal(err)
}
// Recreate bitmap & ensure value does not exist.
if err := tx.DeleteBitmap("x"); err != nil {
t.Fatal(err)
} else if err := tx.CreateBitmap("x"); err != nil {
t.Fatal(err)
} else if ok, err := tx.Contains("x", 1); err != nil {
t.Fatal(err)
} else if ok {
t.Fatal("expected no value in recreated bitmap")
}
}
// deallocateTree had a bug which caused a page to be marked neither free
// nor in-use.
func TestTx_DeallocateTree(t *testing.T) {
db := MustOpenDB(t)
defer MustCloseDB(t, db)
tx := MustBegin(t, db, true)
defer tx.Rollback()
var err error
// Create bitmap & add value.
if err = tx.CreateBitmap("x"); err != nil {
t.Fatal(err)
}
const N = 315
slots := make([]uint64, N)
for i := range slots {
slots[i] = uint64(i) << 20
}
if _, err = tx.Add("x", slots...); err != nil {
t.Fatal(err)
}
if err = tx.Check(); err != nil {
t.Fatalf("check: %v", err)
}
if err = tx.DeleteBitmap("x"); err != nil {
t.Fatal(err)
}
if ok, err := tx.Contains("x", 0); err != nil {
t.Fatal(err)
} else if ok {
t.Fatal("expected no value in recreated bitmap")
}
if err = tx.Check(); err != nil {
t.Fatalf("check: %v", err)
}
}
func arraySizedChunk() []uint16 {
v := make([]uint16, rbf.ArrayMaxSize)
for i := range v {
v[i] = uint16(i)
}
return v
}
var convenientPrepopulatedArray = arraySizedChunk()
// populateBitmapWithArrays
func populateBitmapWithArrays(tb testing.TB, tx *rbf.Tx, n int, name string) {
c := roaring.NewContainerArray(convenientPrepopulatedArray)
for i := 0; i < n; i++ {
err := tx.PutContainer(name, uint64(i), c)
if err != nil {
tb.Fatal(err)
}
}
}
func TestTx_RecreateBitmap(t *testing.T) {
db := MustOpenDB(t)
defer MustCloseDB(t, db)
tx := MustBegin(t, db, true)
defer tx.Rollback()
// Create bitmap & add value.
if err := tx.CreateBitmap("x"); err != nil {
t.Fatal(err)
}
const N = 825
populateBitmapWithArrays(t, tx, N, "x")
err := tx.Commit()
if err != nil {
t.Fatal(err)
}
tx = MustBegin(t, db, true)
defer tx.Rollback()
// Delete bitmap, verifying that it's gone.
if err := tx.DeleteBitmap("x"); err != nil {
t.Fatal(err)
} else {
if ok, err := tx.Contains("x", 0); err != nil {
t.Fatal(err)
} else if ok {
t.Fatal("expected no value in recreated bitmap")
}
}
err = tx.Commit()
if err != nil {
t.Fatal(err)
}
tx = MustBegin(t, db, true)
defer tx.Rollback()
if err := tx.CreateBitmap("x"); err != nil {
t.Fatal(err)
}
populateBitmapWithArrays(t, tx, N, "x")
err = tx.Commit()
if err != nil {
t.Fatal(err)
}
}
func TestTx_RenameBitmap(t *testing.T) {
db := MustOpenDB(t)
defer MustCloseDB(t, db)
tx := MustBegin(t, db, true)
defer tx.Rollback()
// Create bitmap & add value.
if err := tx.CreateBitmap("x"); err != nil {
t.Fatal(err)
} else if _, err := tx.Add("x", 1); err != nil {
t.Fatal(err)
}
// Rename bitmap & ensure value still exists.
if err := tx.RenameBitmap("x", "y"); err != nil {
t.Fatal(err)
} else if ok, err := tx.Contains("y", 1); err != nil {
t.Fatal(err)
} else if !ok {
t.Fatal("expected value in renamed bitmap")
}
}
func TestTx_Add_Quick(t *testing.T) {
if testing.Short() {
t.Skip("-short enabled, skipping")
} else if is32Bit() {
t.Skip("32-bit build, skipping quick check tests")
}
QuickCheck(t, func(t *testing.T, rand *rand.Rand) {
t.Parallel()
db := MustOpenDB(t)
defer MustCloseDB(t, db)
tx := MustBegin(t, db, true)
defer tx.Rollback()
values := GenerateValues(rand, 10000)
if err := tx.CreateBitmap("x"); err != nil {
t.Fatal(err)
}
// Insert values in random order.
for _, i := range rand.Perm(len(values)) {
v := values[i]
if _, err := tx.Add("x", v); err != nil {
t.Fatalf("Add(%d) i=%d err=%q", v, i, err)
}
}
// Verify all bits are written.
for i, v := range values {
if ok, err := tx.Contains("x", v); !ok || err != nil {
t.Fatalf("Contains(%d)=(%v,%v) i=%d hi=%d lo=%d", v, ok, err, i, highbits(v), lowbits(v))
}
}
})
}
func TestTx_DeallocateToFreeList(t *testing.T) {
db := MustOpenDB(t)
defer MustCloseDB(t, db)
tx := MustBegin(t, db, true)
defer tx.Rollback()
var err error
// Create bitmap & add value.
if err = tx.CreateBitmap("x"); err != nil {
t.Fatal(err)
}
if err = tx.CreateBitmap("y"); err != nil {
t.Fatal(err)
}
// Insert large array values.
populateBitmapWithArrays(t, tx, 4080, "x")
if err = tx.Check(); err != nil {
t.Fatal(err)
}
for i := 0; i < 500; i++ {
if _, err := tx.Add("y", uint64(i)<<16+32768); err != nil {
t.Fatal(err)
}
}
if err = tx.Check(); err != nil {
t.Fatal(err)
}
if err := tx.DeleteBitmap("y"); err != nil {
t.Fatal(err)
}
if err = tx.Check(); err != nil {
t.Fatal(err)
}
if err = tx.Commit(); err != nil {
t.Fatal(err)
}
tx = MustBegin(t, db, true)
defer tx.Rollback()
// Delete bitmap, verifying that it's gone.
if err := tx.DeleteBitmap("x"); err != nil {
t.Fatal(err)
} else {
if ok, err := tx.Contains("x", 0); err != nil {
t.Fatal(err)
} else if ok {
t.Fatal("expected no value in recreated bitmap")
}
}
if err = tx.Check(); err != nil {
t.Fatal(err)
}
if err = tx.Commit(); err != nil {
t.Fatal(err)
}
tx = MustBegin(t, db, true)
defer tx.Rollback()
if err := tx.CreateBitmap("x"); err != nil {
t.Fatal(err)
}
populateBitmapWithArrays(t, tx, 4080, "x")
if err = tx.Check(); err != nil {
t.Fatal(err)
}
if err = tx.Commit(); err != nil {
t.Fatal(err)
}
}
func TestTx_RemoveContainer(t *testing.T) {
t.Parallel()
db := MustOpenDB(t)
defer MustCloseDB(t, db)
tx := MustBegin(t, db, true)
defer tx.Rollback()
if err := tx.CreateBitmap("x"); err != nil {
t.Fatal(err)
}
// Insert large array values.
populateBitmapWithArrays(t, tx, 500, "x")
if err := tx.Commit(); err != nil {
t.Fatal(err)
}
tx = MustBegin(t, db, true)
defer tx.Rollback()
// Remove all array values.
for i := 0; i < 500; i++ {
err := tx.RemoveContainer("x", uint64(i))
if err != nil {
t.Fatal(err)
}
}
// This triggered a different panic without the relevant patch.
err := tx.RemoveContainer("x", 500)
if err != nil {
t.Fatal(err)
}
if err := tx.Commit(); err != nil {
t.Fatal(err)
}
}
func TestTx_AddRemove_Quick(t *testing.T) {
if testing.Short() {
t.Skip("-short enabled, skipping")
} else if is32Bit() {
t.Skip("32-bit build, skipping quick check tests")
}
QuickCheck(t, func(t *testing.T, rand *rand.Rand) {
t.Parallel()
db := MustOpenDB(t)
defer MustCloseDB(t, db)
tx := MustBegin(t, db, true)
defer tx.Rollback()
values := GenerateValues(rand, 10000)
if err := tx.CreateBitmap("x"); err != nil {
t.Fatal(err)
}
// Insert values in random order.
for _, i := range rand.Perm(len(values)) {
if _, err := tx.Add("x", values[i]); err != nil {
t.Fatalf("Add(%d) i=%d err=%q", values[i], i, err)
}
}
// Remove half the values in random order.
for _, i := range rand.Perm(len(values)) {
if _, err := tx.Remove("x", values[i]); err != nil {
t.Fatalf("Remove(%d) i=%d err=%q", values[i], i, err)
}
}
// Verify all bits are removed.
for i, v := range values {
if ok, err := tx.Contains("x", v); ok || err != nil {
t.Fatalf("Contains(%d)=(%v,%v) i=%d hi=%d lo=%d", v, ok, err, i, highbits(v), lowbits(v))
}
}
// Re-add those values back in.
for _, i := range rand.Perm(len(values)) {
if _, err := tx.Add("x", values[i]); err != nil {
t.Fatalf("Re-Add(%d) i=%d err=%q", values[i], i, err)
}
}
// Verify all bits are written.
for i, v := range values {
if ok, err := tx.Contains("x", v); !ok || err != nil {
t.Fatalf("Contains(%d)=(%v,%v) i=%d hi=%d lo=%d", v, ok, err, i, highbits(v), lowbits(v))
}
}
})
}
func TestTx_Remove(t *testing.T) {
t.Run("FullContiguous", func(t *testing.T) {
if testing.Short() {
t.Skip("-short enabled, skipping")
}
for _, bitN := range []uint64{1000, 100000, 2000000} {
t.Run(fmt.Sprint(bitN), func(t *testing.T) {
db := MustOpenDB(t)
defer MustCloseDB(t, db)
// Add bits
func() {
tx := MustBegin(t, db, true)
defer tx.Rollback()
if err := tx.CreateBitmap("x"); err != nil {
t.Fatal(err)
}
for i := uint64(0); i < bitN; i++ {
if _, err := tx.Add("x", i); err != nil {
t.Fatalf("Add(%d) err=%q", i, err)
}
}
if err := tx.Commit(); err != nil {
t.Fatal(err)
}
}()
// Remove bits
func() {
tx := MustBegin(t, db, true)
defer tx.Rollback()
for i := uint64(0); i < bitN; i++ {
if _, err := tx.Remove("x", i); err != nil {
t.Fatalf("Remove(%d) err=%q", i, err)
}
}
if err := tx.Commit(); err != nil {
t.Fatal(err)
}
}()
// Verify that all bits have been removed.
tx := MustBegin(t, db, false)
defer tx.Rollback()
if n, err := tx.Count("x"); err != nil {
t.Fatal(err)
} else if got, want := n, uint64(0); got != want {
t.Fatalf("Count=%d, want %d", got, want)
}
})
}
})
t.Run("PartialContiguous", func(t *testing.T) {
db := MustOpenDB(t)
defer MustCloseDB(t, db)
// Add bits
const bitN = 100000
const multiplier = 7 // space out bits so we span more containers
func() {
tx := MustBegin(t, db, true)
defer tx.Rollback()
if err := tx.CreateBitmap("x"); err != nil {
t.Fatal(err)
}
for i := uint64(0); i < bitN; i++ {
if _, err := tx.Add("x", i*multiplier); err != nil {
t.Fatalf("Add(%d) err=%q", i, err)
}
}
if err := tx.Commit(); err != nil {
t.Fatal(err)
}
}()
// Remove some bits in small contiguous chunks.
var deleteN int
for i := uint64(bitN / 2); i < bitN; {
func() {
tx := MustBegin(t, db, true)
defer tx.Rollback()
for j := uint64(0); j < 100; i, j = i+1, j+1 {
if n, err := tx.Remove("x", i*multiplier); err != nil || n != 1 {
t.Fatalf("Remove(%d)=(%v,%q)", i, n, err)
}
deleteN++
}
if err := tx.Commit(); err != nil {
t.Fatal(err)
}
}()
}
// Verify that we have the correct count afterward.
tx := MustBegin(t, db, false)
defer tx.Rollback()
if n, err := tx.Count("x"); err != nil {
t.Fatal(err)
} else if got, want := n, uint64(bitN-deleteN); got != want {
t.Fatalf("Count=%d, want %d", got, want)
}
})
t.Run("PartialNonContiguous", func(t *testing.T) {
db := MustOpenDB(t)
defer MustCloseDB(t, db)
// Add bits
const bitN = 100000
const multiplier = 7 // space out bits
bits := make([]uint64, 0, bitN)
func() {
tx := MustBegin(t, db, true)
defer tx.Rollback()
if err := tx.CreateBitmap("x"); err != nil {
t.Fatal(err)
}
for i := uint64(0); i < bitN; i++ {
if _, err := tx.Add("x", i*multiplier); err != nil {
t.Fatalf("Add(%d) err=%q", i, err)
}
bits = append(bits, i*multiplier)
}
if err := tx.Commit(); err != nil {
t.Fatal(err)
}
}()
// Remove some bits in small contiguous chunks.
var deleteN int
perm := rand.Perm(len(bits))
for i := uint64(bitN / 2); i < bitN; {
func() {
tx := MustBegin(t, db, true)
defer tx.Rollback()
for j := uint64(0); j < 100; i, j = i+1, j+1 {
value := bits[perm[i]]
if n, err := tx.Remove("x", value); err != nil || n != 1 {
t.Fatalf("Remove(%d)=(%v,%q)", value, n, err)
}
deleteN++
}
if err := tx.Commit(); err != nil {
t.Fatal(err)
}
}()
}
// Verify that we have the correct count afterward.
tx := MustBegin(t, db, false)
defer tx.Rollback()
if n, err := tx.Count("x"); err != nil {
t.Fatal(err)
} else if got, want := n, uint64(bitN-deleteN); got != want {
t.Fatalf("Count=%d, want %d", got, want)
}
})
t.Run("DeleteEmptyBitmap", func(t *testing.T) {
db := MustOpenDB(t)
defer MustCloseDB(t, db)
// Create bitmap.
func() {
tx := MustBegin(t, db, true)
defer tx.Rollback()
if err := tx.CreateBitmap("x"); err != nil {
t.Fatal(err)
} else if err := tx.Commit(); err != nil {
t.Fatal(err)
}
}()
// Remove bitmap.
func() {
tx := MustBegin(t, db, true)
defer tx.Rollback()
if err := tx.DeleteBitmap("x"); err != nil {
t.Fatal(err)
} else if err := tx.Commit(); err != nil {
t.Fatal(err)
}
}()
// Ensure bitmap no longer exists.
tx := MustBegin(t, db, false)
defer tx.Rollback()
if exists, err := tx.BitmapExists("x"); err != nil {
t.Fatal(err)
} else if exists {
t.Fatal("expected bitmap to be removed")
}
})
t.Run("WithTreeDepth", func(t *testing.T) {
for depth := 1; depth <= 3; depth++ {
t.Run(fmt.Sprint(depth), func(t *testing.T) {
db := MustOpenDB(t)
defer MustCloseDB(t, db)
// Create bitmap & insert until we hit a tree depth.
var bitN int
func() {
tx := MustBegin(t, db, true)
defer tx.Rollback()
if err := tx.CreateBitmap("x"); err != nil {
t.Fatal(err)
}
for i := uint64(0); ; i++ {
if _, err := tx.Add("x", i<<16); err != nil {
t.Fatalf("Add(%d) err=%q", i<<16, err)
}
bitN++
if d, err := tx.Depth("x"); err != nil {
t.Fatal(err)
} else if d == depth {
break
}
}
if err := tx.Commit(); err != nil {
t.Fatal(err)
}
}()
// Remove all bits in reverse order.
func() {
tx := MustBegin(t, db, true)
defer tx.Rollback()
for i := bitN - 1; i >= 0; i-- {
if n, err := tx.Remove("x", uint64(i)<<16); err != nil || n != 1 {
t.Fatalf("Remove(%d)=(%v,%q)", uint64(i)<<16, n, err)
}
}
if err := tx.Commit(); err != nil {
t.Fatal(err)
}
}()
// Ensure bitmap no longer exists.
tx := MustBegin(t, db, false)
defer tx.Rollback()
for i := uint64(0); i < uint64(bitN); i++ {
if ok, err := tx.Contains("x", i<<16); err != nil || ok {
t.Fatalf("Contains(%d)=(%v,%q)", i<<16, ok, err)
}
}
})
}
})
t.Run("RollbackAfterDelete", func(t *testing.T) {
db := MustOpenDB(t)
defer MustCloseDB(t, db)
func() {
tx := MustBegin(t, db, true)
defer tx.Rollback()
if err := tx.CreateBitmap("x"); err != nil {
t.Fatal(err)
} else if err := tx.Commit(); err != nil {
t.Fatal(err)
}
}()
// Add bits
const bitN = 1000
for i := uint64(0); i < bitN; i++ {
func() {
tx := MustBegin(t, db, true)
defer tx.Rollback()
if _, err := tx.Add("x", i<<16); err != nil {
t.Fatalf("Add(%d) err=%q", i<<16, err)
}
// Only commit every other bit.
if i%2 == 1 {
if err := tx.Commit(); err != nil {
t.Fatal(err)
}
}
}()
}
// Verify that we have the correct count afterward.
tx := MustBegin(t, db, false)
defer tx.Rollback()
if n, err := tx.Count("x"); err != nil {
t.Fatal(err)
} else if got, want := n, uint64(bitN/2); got != want {
t.Fatalf("Count=%d, want %d", got, want)
}
})
}
func TestTx_Multiple_CreateBitmap(t *testing.T) {
rand := rand.New(rand.NewSource(0))
db := MustOpenDB(t)
defer MustCloseDB(t, db)
tx := MustBegin(t, db, true)
defer tx.Rollback()
values := GenerateValues(rand, 2)
if err := tx.CreateBitmap("x/1"); err != nil {
t.Fatal(err)
}
// Insert values in random order.
for _, i := range rand.Perm(len(values)) {
if _, err := tx.Add("x/1", values[i]); err != nil {
t.Fatalf("Add(%d) i=%d err=%q", values[i], i, err)
}
}
if err := tx.Commit(); err != nil {
t.Fatalf("Commit 1 err=%q", err)
}
tx1 := MustBegin(t, db, true)
defer tx1.Rollback()
if err := tx1.CreateBitmap("x/2"); err != nil {
t.Fatal(err)
}
// Insert values in random order.
for _, i := range rand.Perm(len(values)) {
if _, err := tx1.Add("x/2", values[i]); err != nil {
t.Fatalf("Add(%d) i=%d err=%q", values[i], i, err)
}
}
if err := tx1.Commit(); err != nil {
t.Fatalf("Commit 2 err=%q", err)
}
}
func TestTx_CursorCrashArray(t *testing.T) {
db := MustOpenDB(t)
defer MustCloseDB(t, db)
tx := MustBegin(t, db, true)
defer tx.Rollback()
if err := tx.CreateBitmap("x"); err != nil {
t.Fatal(err)
}
c, err := tx.Cursor("x")
if err != nil {
t.Fatal(err)
}
//setArray(t, 0, 2379, &c)
//setArray(t, 1, 2337, &c)
setArray(t, 32, 1216, c)
setArray(t, 33, 1195, c)
setArray(t, 48, 1186, c)
setArray(t, 49, 1223, c)
setArray(t, 50, 1223, c)
}
func TestTx_CursorCrashBitmap(t *testing.T) {
if testing.Short() {
t.Skip("-short enabled, skipping")
}
db := MustOpenDB(t)
defer MustCloseDB(t, db)
tx := MustBegin(t, db, true)
defer tx.Rollback()
if err := tx.CreateBitmap("x"); err != nil {
t.Fatal(err)
}
c, err := tx.Cursor("x")
if err != nil {
t.Fatal(err)
}
setArray(t, 0, 22510, c)
setArray(t, 1, 23584, c)
}
func setArray(tb testing.TB, key, num int, c *rbf.Cursor) {
for i := uint64(0); i < uint64(num); i++ {
v := i | (uint64(key) << 16)
if _, err := c.Add(v); err != nil {
tb.Fatal(err)
}
}
}
func BenchmarkTx_Add(b *testing.B) {
for _, n := range []int{1, 10, 1000} {
b.Run(fmt.Sprint(n), func(b *testing.B) {
rand := rand.New(rand.NewSource(0))
values := make([]uint64, n)
for i := range values {
values[i] = uint64(rand.Intn(rbf.ShardWidth))
}
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
func() {
db := MustOpenDB(b)
defer MustCloseDB(b, db)
for _, v := range values {
func() {
tx := MustBegin(b, db, true)
defer tx.Rollback()
if _, err := tx.Add("x", v); err != nil {
b.Fatal(err)
} else if err := tx.Commit(); err != nil {
b.Fatal(err)
}
}()
}
}()
}
})
}
}
func BenchmarkTx_Contains(b *testing.B) {
for _, n := range []int{10000, 100000, 1000000} {
b.Run(fmt.Sprint(n), func(b *testing.B) {
rand := rand.New(rand.NewSource(0))
values := make([]uint64, n)
for i := range values {
values[i] = uint64(rand.Intn(rbf.ShardWidth))
}
db := MustOpenDB(b)
defer MustCloseDB(b, db)
tx := MustBegin(b, db, true)
defer tx.Rollback()
b.ResetTimer()
t := time.Now()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for _, v := range values {
if _, err := tx.Contains("x", v); err != nil {
b.Fatalf("Contains(%d) i=%d err=%q", v, i, err)
}
}
}
b.ReportMetric(float64(time.Since(t).Nanoseconds())/float64(n*b.N), "ns/op")
})
}
}
func TestTx_CreateBitmap(t *testing.T) {
t.Run("Bulk", func(t *testing.T) {
db := MustOpenDB(t)
defer MustCloseDB(t, db)
tx, err := db.Begin(true)
if err != nil {
t.Fatal(err)
}
defer tx.Rollback()
if err := tx.CreateBitmap(fmt.Sprintf("%4000x", 0)); err != nil {
t.Fatal(err)
} else if err := tx.CreateBitmap(fmt.Sprintf("%4000x", 1)); err != nil {
t.Fatal(err)
} else if err := tx.CreateBitmap(fmt.Sprintf("%4000x", 2)); err != nil {
t.Fatal(err)
}
if err := tx.Commit(); err != nil {
t.Fatal(err)
}
})
}
func TestTx_DeleteBitmapsWithPrefix(t *testing.T) {
db := MustOpenDB(t)
defer MustCloseDB(t, db)
prefix := "abc"
bitmapSize := 10000
// var err error
// create a interleaved set up array and bitmap containers
bits := make([]uint64, bitmapSize)
x := uint64(1)
for i := 0; i < len(bits); i++ {
bits[i] = x
x = x + 2
}
ifError := func(err error) {
if err != nil {
t.Fatal(err)
}
}
var b bytes.Buffer
pBuf := func(msg string, args ...interface{}) (int, error) {
return fmt.Fprintf(&b, msg, args...)
}
checkInfos := func(pf func(string, ...interface{}) (int, error)) {
tx := MustBegin(t, db, false)
defer tx.Rollback()
infos, err := tx.PageInfos()
ifError(err)
for pgno, info := range infos {
switch info := info.(type) {
case *rbf.MetaPageInfo:
pf("%-8d ", pgno)
pf("%-10s ", "meta")
pf("pageN=%d,walid=%d,rootrec=%d,freelist=%d\n", info.PageN, info.WALID, info.RootRecordPageNo, info.FreelistPageNo)
case *rbf.RootRecordPageInfo:
pf("%-8d ", pgno)
pf("%-10s ", "rootrec")
pf("next=%d\n", info.Next)
case *rbf.LeafPageInfo:
pf("%-8d ", pgno)
pf("%-10s ", "leaf")
pf("flags=x%x,celln=%d\n", info.Flags, info.CellN)
case *rbf.BranchPageInfo:
pf("%-8d ", pgno)
pf("%-10s ", "branch")
pf("flags=x%x,celln=%d\n", info.Flags, info.CellN)
case *rbf.BitmapPageInfo:
pf("%-8d ", pgno)
pf("%-10s ", "bitmap")
pf("-\n")
case *rbf.FreePageInfo:
pf("%-8d ", pgno)
pf("%-10s ", "free")
pf("-\n")
default:
t.Fatal(fmt.Sprintf("unexpected page info type %T", info))
}
}
}
populate := func() {
tx := MustBegin(t, db, true)
defer tx.Rollback()
for i := uint64(0); i < 16; i++ {
bm := roaring.NewBitmap()
if i%3 == 0 {
bm.Put(i, roaring.NewContainerBitmap(6144, bits))
if _, err := tx.AddRoaring(prefix, bm); err != nil {
panic(err)
}
} else {
bm.Put(i, roaring.NewContainerArray([]uint16{2, 4, 5, 7}))
if _, err := tx.AddRoaring(prefix, bm); err != nil {
panic(err)
}
}
}
ifError(tx.Commit())
}
checkInfos(pBuf)
populate()
checkInfos(pBuf)
ifError(db.Check())
tx := MustBegin(t, db, true)
tx.DeleteBitmapsWithPrefix(prefix)
ifError(tx.Commit())
ifError(db.Check())
checkInfos(pBuf)
populate()
ifError(db.Check())
checkInfos(pBuf)
}
func TestTx_Check(t *testing.T) {
t.Run("EmptyBranchPage", func(t *testing.T) {
t.Parallel()
db := MustOpenDB(t)
defer MustCloseDBNoCheck(t, db)
tx := MustBegin(t, db, true)
defer tx.Rollback()
if err := tx.CreateBitmap("x"); err != nil {
t.Fatal(err)
}
// Insert enough array containers to split page.
for i := 0; i < 1000; i++ {
if _, err := tx.Add("x", uint64(i<<16)); err != nil {
t.Fatalf("Add(%d) err=%q", i<<16, err)
}
}
// Read page types for all pages.
infos, err := tx.PageInfos()
if err != nil {
t.Fatal(err)
}
// Commit & checkpoint to flush to the data file.
if err := tx.Commit(); err != nil {
t.Fatal(err)
} else if err := db.Checkpoint(); err != nil {
t.Fatal(err)
}
// Corrupt first branch page found by zeroing out the cell count.
var pgno uint32
for _, info := range infos {
if info, ok := info.(*rbf.BranchPageInfo); ok {
pgno = info.Pgno
page := mustReadPage(t, db.DataPath(), pgno)
binary.BigEndian.PutUint16(page[8:10], 0) // zero cell count
mustWritePage(t, db.DataPath(), pgno, page)
break
}
}
// Verify that check now returns an error.
if err := db.Check(); err == nil || !strings.Contains(err.Error(), fmt.Sprintf("branch page %d is empty", pgno)) {
t.Fatalf("unexpected error: %#v", err)
}
})
t.Run("ErrBadFreelist", func(t *testing.T) {
t.Parallel()
db := MustOpenDBAt(t, filepath.Join("testdata", "check", "bad-freelist"))
defer db.Close()
tx := MustBegin(t, db, false)
defer tx.Rollback()
if err, ok := tx.Check().(rbf.ErrorList); !ok {
t.Fatal("expected error list")
} else if s := err.FullError(); !strings.Contains(s, `branch cell index out of range: pgno=2 i=0 n=0`) {
t.Fatalf("unexpected error:\n%s", s)
}
})
t.Run("ErrBadBitmap", func(t *testing.T) {
t.Parallel()
db := MustOpenDBAt(t, filepath.Join("testdata", "check", "bad-bitmap"))
defer db.Close()
tx := MustBegin(t, db, false)
defer tx.Rollback()
if err, ok := tx.Check().(rbf.ErrorList); !ok {
t.Fatal("expected error list")
} else if s := err.FullError(); !strings.Contains(s, `cannot read page: pgno=65537 parent=3 err=rbf: page read out of bounds: pgno=65537 max=3`) {
t.Fatalf("unexpected error:\n%s", s)
}
})
}
func mustReadPage(tb testing.TB, path string, pgno uint32) []byte {
tb.Helper()
f, err := os.Open(path)
if err != nil {
tb.Fatal(err)
}
defer f.Close()
buf := make([]byte, rbf.PageSize)
if _, err := f.ReadAt(buf, int64(pgno)*rbf.PageSize); err != nil {
tb.Fatal(err)
}
return buf
}
func mustWritePage(tb testing.TB, path string, pgno uint32, buf []byte) {
tb.Helper()
f, err := os.OpenFile(path, os.O_WRONLY, 0666)
if err != nil {
tb.Fatal(err)
}
defer f.Close()
if _, err := f.WriteAt(buf, int64(pgno)*rbf.PageSize); err != nil {
tb.Fatal(err)
}
}