featurebase/rbf/cursor_test.go
Seebs 7d1fde7438 drop anti-entropy feature, since it doesn't work
The anti-entropy feature has never actually worked. We've been
talking about removing it or replacing it for ages, but haven't
had a concrete motivation.

But the anti-entropy interface is the sole user of several components
of the Tx interface, and now that we're trying to replace that
interface, being able to drop those components has some appeal, so
let's remove the one thing that used them, in the hopes that this
will simplify life.

This also lets us drop ForEach and ForEachRange, which were
barely used at all. The one surviving usage (CSV export) can be
handled by using the container iterator we already have, and
making ContainerCallback exported so we can use it to just call
things for every bit.

(cherry picked from commit fff9ddc1f5)
2022-11-15 11:31:13 -08:00

1233 lines
29 KiB
Go

// Copyright 2022 Molecula Corp. (DBA FeatureBase).
// SPDX-License-Identifier: Apache-2.0
package rbf_test
import (
"bytes"
"io"
"math/bits"
"math/rand"
"os"
"reflect"
"sort"
"strings"
"testing"
"github.com/featurebasedb/featurebase/v3/rbf"
"github.com/featurebasedb/featurebase/v3/roaring"
"github.com/stretchr/testify/assert"
)
func TestCursor_FirstNext(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)
} else if _, err := tx.Add("x", 0x00000001, 0x00000002, 0x00010003, 0x00030004); err != nil {
t.Fatal(err)
}
c, err := tx.Cursor("x")
if err != nil {
t.Fatal(err)
} else if err := c.First(); err != nil {
t.Fatal(err)
}
if err := c.Next(); err != nil {
t.Fatal(err)
} else if got, want := c.Key(), uint64(0); got != want {
t.Fatalf("Next()=%d, want %d", got, want)
} else if got, want := c.Values(), []uint16{1, 2}; !reflect.DeepEqual(got, want) {
t.Fatalf("Values()=%#v, want %#v", got, want)
}
if err := c.Next(); err != nil {
t.Fatal(err)
} else if got, want := c.Key(), uint64(1); got != want {
t.Fatalf("Next()=%d, want %d", got, want)
} else if got, want := c.Values(), []uint16{3}; !reflect.DeepEqual(got, want) {
t.Fatalf("Values()=%#v, want %#v", got, want)
}
if err := c.Next(); err != nil {
t.Fatal(err)
} else if got, want := c.Key(), uint64(3); got != want {
t.Fatalf("Next()=%d, want %d", got, want)
} else if got, want := c.Values(), []uint16{4}; !reflect.DeepEqual(got, want) {
t.Fatalf("Values()=%#v, want %#v", got, want)
}
}
func TestCursor_FirstNext_Quick(t *testing.T) {
if testing.Short() {
t.Skip("-short enabled, skipping")
} else if is32Bit() {
t.Skip("32-bit build, skipping quick check tests")
}
const n = 10000
QuickCheck(t, func(t *testing.T, rand *rand.Rand) {
t.Parallel()
// Generate sorted list of values.
values := make([]uint64, rand.Intn(n))
for i := range values {
values[i] = uint64(rand.Intn(rbf.ShardWidth))
}
sort.Slice(values, func(i, j int) bool { return values[i] < values[j] })
db := MustOpenDB(t)
defer MustCloseDB(t, db)
tx := MustBegin(t, db, true)
defer tx.Rollback()
// Insert values in random order.
if err := tx.CreateBitmap("x"); err != nil {
t.Fatal(err)
}
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)
}
}
// Generate unique bucketed values.
type Item struct {
key uint64
values []uint16
}
var items []Item
m := make(map[uint64]struct{})
for _, v := range values {
if _, ok := m[v]; ok {
continue
}
m[v] = struct{}{}
hi, lo := highbits(v), lowbits(v)
if len(items) == 0 || items[len(items)-1].key != hi {
items = append(items, Item{key: hi})
}
item := &items[len(items)-1]
item.values = append(item.values, lo)
}
// Verify cursor returns correct value groups.
c, err := tx.Cursor("x")
if err != nil {
t.Fatal(err)
} else if err := c.First(); err != nil {
t.Fatal(err)
}
for _, item := range items {
if err := c.Next(); err != nil {
t.Fatal(err)
} else if got, want := c.Key(), item.key; got != want {
t.Fatalf("Key()=%d, want %d", got, want)
} else if got, want := c.Values(), item.values; !reflect.DeepEqual(got, want) {
t.Fatalf("len(Values())=%v/%#v, want %v/%#v", len(got), got, len(want), want)
}
}
})
}
func TestCursor_LastPrev(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)
} else if _, err := tx.Add("x", 0x00000001, 0x00000002, 0x00010003, 0x00030004); err != nil {
t.Fatal(err)
}
c, err := tx.Cursor("x")
if err != nil {
t.Fatal(err)
} else if err := c.Last(); err != nil {
t.Fatal(err)
}
if err := c.Prev(); err != nil {
t.Fatal(err)
} else if got, want := c.Key(), uint64(3); got != want {
t.Fatalf("Prev()=%d, want %d", got, want)
} else if got, want := c.Values(), []uint16{4}; !reflect.DeepEqual(got, want) {
t.Fatalf("Values()=%#v, want %#v", got, want)
}
if err := c.Prev(); err != nil {
t.Fatal(err)
} else if got, want := c.Key(), uint64(1); got != want {
t.Fatalf("Prev()=%d, want %d", got, want)
} else if got, want := c.Values(), []uint16{3}; !reflect.DeepEqual(got, want) {
t.Fatalf("Values()=%#v, want %#v", got, want)
}
if err := c.Prev(); err != nil {
t.Fatal(err)
} else if got, want := c.Key(), uint64(0); got != want {
t.Fatalf("Prev()=%d, want %d", got, want)
} else if got, want := c.Values(), []uint16{1, 2}; !reflect.DeepEqual(got, want) {
t.Fatalf("Values()=%#v, want %#v", got, want)
}
}
func TestCursor_LastPrev_Quick(t *testing.T) {
if testing.Short() {
t.Skip("-short enabled, skipping")
} else if is32Bit() {
t.Skip("32-bit build, skipping quick check tests")
}
const n = 10000
QuickCheck(t, func(t *testing.T, rand *rand.Rand) {
t.Parallel()
// Generate sorted list of values.
values := make([]uint64, n)
for i := range values {
values[i] = uint64(rand.Intn(rbf.ShardWidth))
}
sort.Slice(values, func(i, j int) bool { return values[i] < values[j] })
db := MustOpenDB(t)
defer MustCloseDB(t, db)
tx := MustBegin(t, db, true)
defer tx.Rollback()
// Insert values in random order.
if err := tx.CreateBitmap("x"); err != nil {
t.Fatal(err)
}
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)
}
}
// Generate unique bucketed values.
type Item struct {
key uint64
values []uint16
}
var items []Item
m := make(map[uint64]struct{})
for _, v := range values {
if _, ok := m[v]; ok {
continue
}
m[v] = struct{}{}
hi, lo := highbits(v), lowbits(v)
if len(items) == 0 || items[len(items)-1].key != hi {
items = append(items, Item{key: hi})
}
item := &items[len(items)-1]
item.values = append(item.values, lo)
}
// Verify cursor returns correct value groups.
c, err := tx.Cursor("x")
if err != nil {
t.Fatal(err)
} else if err := c.Last(); err != nil {
t.Fatal(err)
}
for i := len(items) - 1; i >= 0; i-- {
if err := c.Prev(); err != nil {
t.Fatal(err)
} else if got, want := c.Key(), items[i].key; got != want {
t.Fatalf("Key()=%d, want %d", got, want)
} else if got, want := c.Values(), items[i].values; !reflect.DeepEqual(got, want) {
t.Fatalf("len(Values())=%v, want %v", len(got), len(want))
}
}
})
}
func makeBitmap(bit []uint16) (n int, ret []uint64) {
ret = make([]uint64, 1024)
for _, v := range bit {
ret[v/64] |= 1 << uint64(v%64)
}
n = 0
for _, v := range ret {
n += bits.OnesCount64(v)
}
return
}
func TestCursor_AddRoaring(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)
}
tests := []struct {
name string
fieldview string
rb *roaring.Bitmap
wantChanged bool
wantErr bool
}{{
name: "no view",
fieldview: "a/standard",
rb: func() *roaring.Bitmap {
bm := roaring.NewBitmap()
return bm
}(),
wantChanged: false,
wantErr: false},
{
name: "initial Array",
fieldview: "x",
rb: func() *roaring.Bitmap {
bm := roaring.NewBitmap()
bm.Put(0, roaring.NewContainerArray([]uint16{1, 2}))
return bm
}(),
wantChanged: true,
wantErr: false}, {
name: "initial RLE",
fieldview: "x",
rb: func() *roaring.Bitmap {
bm := roaring.NewBitmap()
bm.Put(1, roaring.NewContainerRun([]roaring.Interval16{{Start: 10, Last: 20000}}))
return bm
}(),
wantChanged: true,
wantErr: false},
{
name: "initial Bitmap",
fieldview: "x",
rb: func() *roaring.Bitmap {
bm := roaring.NewBitmap()
bm.Put(3, roaring.NewContainerBitmap(makeBitmap([]uint16{4, 8, 12})))
return bm
}(),
wantChanged: true,
wantErr: false},
{
name: "merge Array exist",
fieldview: "x",
rb: func() *roaring.Bitmap {
bm := roaring.NewBitmap()
bm.Put(0, roaring.NewContainerArray([]uint16{1, 2}))
return bm
}(),
wantChanged: false,
wantErr: false}, {
name: "merge Array present",
fieldview: "x",
rb: func() *roaring.Bitmap {
bm := roaring.NewBitmap()
bm.Put(0, roaring.NewContainerArray([]uint16{3, 4}))
return bm
}(),
wantChanged: true,
wantErr: false},
{
name: "merge Bitmap exist",
fieldview: "x",
rb: func() *roaring.Bitmap {
bm := roaring.NewBitmap()
bm.Put(3, roaring.NewContainerBitmap(makeBitmap([]uint16{4, 8, 12})))
return bm
}(),
wantChanged: false,
wantErr: false},
{
name: "merge Bitmap ",
fieldview: "x",
rb: func() *roaring.Bitmap {
bm := roaring.NewBitmap()
bm.Put(3, roaring.NewContainerBitmap(makeBitmap([]uint16{75})))
return bm
}(),
wantChanged: true,
wantErr: false},
{
name: "merge BitmapArray ",
fieldview: "x",
rb: func() *roaring.Bitmap {
bm := roaring.NewBitmap()
bm.Put(0, roaring.NewContainerBitmap(makeBitmap([]uint16{75})))
return bm
}(),
wantChanged: true,
wantErr: false}, {
name: "too Big Array ",
fieldview: "x",
rb: func() *roaring.Bitmap {
bm := roaring.NewBitmap()
items := make([]uint16, rbf.ArrayMaxSize+2)
for i := 0; i < len(items); i++ {
items[i] = uint16(i)
}
bm.Put(10, roaring.NewContainerArray(items))
return bm
}(),
wantChanged: true,
wantErr: false},
{
name: "too Big RLE ",
fieldview: "x",
rb: func() *roaring.Bitmap {
bm := roaring.NewBitmap()
items := make([]roaring.Interval16, rbf.RLEMaxSize+2)
x := uint16(0)
for i := 0; i < len(items); i++ {
v := roaring.Interval16{Start: x, Last: x + 1}
x += 3
items[i] = v
}
bm.Put(10, roaring.NewContainerRun(items))
return bm
}(),
wantChanged: true,
wantErr: false}, {
name: "empty container ",
fieldview: "x",
rb: func() *roaring.Bitmap {
bm := roaring.NewBitmap()
bm.Put(11, roaring.NewContainerArray([]uint16{}))
return bm
}(),
wantChanged: false,
wantErr: false},
{
name: "merge RLE",
fieldview: "x",
rb: func() *roaring.Bitmap {
bm := roaring.NewBitmap()
bm.Put(1, roaring.NewContainerRun([]roaring.Interval16{{Start: 1, Last: 12}}))
return bm
}(),
wantChanged: true,
wantErr: false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotChanged, err := tx.AddRoaring(tt.fieldview, tt.rb)
if (err != nil) != tt.wantErr {
t.Errorf("Cursor.AddRoaring() error = %v, wantErr %v", err, tt.wantErr)
return
}
if gotChanged != tt.wantChanged {
t.Errorf("Cursor.AddRoaring() = %v, want %v", gotChanged, tt.wantChanged)
}
})
}
}
func TestCursor_RLETesting(t *testing.T) {
db := MustOpenDB(t)
defer MustCloseDB(t, db)
tx := MustBegin(t, db, true)
defer tx.Rollback()
//setup RLE
if err := tx.CreateBitmap("x"); err != nil {
t.Fatal(err)
}
rb := func() *roaring.Bitmap {
bm := roaring.NewBitmap()
bm.Put(0, roaring.NewContainerRun([]roaring.Interval16{{Start: 10, Last: 11}}))
return bm
}()
_, err := tx.AddRoaring("x", rb)
if err != nil {
t.Errorf("Add Roaring Failed %v", err)
}
//
tests := []struct {
name string
args []uint64
want []uint16
wantChanged bool
wantErr bool
}{{
name: "update run at Last",
args: []uint64{0x0000000c},
want: []uint16{0x0000000a, 0x0000000b, 0x0000000c},
wantChanged: true,
wantErr: false,
},
{
name: "update run at begining",
args: []uint64{0x00000001, 0x00000002},
want: []uint16{0x00000001, 0x00000002, 0x0000000a, 0x0000000b, 0x0000000c},
wantChanged: true,
wantErr: false,
},
{
name: "add run at end",
args: []uint64{0x0000000f},
want: []uint16{0x00000001, 0x00000002, 0x0000000a, 0x0000000b, 0x0000000c, 0x0000000f},
wantChanged: true,
wantErr: false,
},
{
name: "no change",
args: []uint64{0x0000000b},
want: []uint16{0x00000001, 0x00000002, 0x0000000a, 0x0000000b, 0x0000000c, 0x0000000f},
wantChanged: false,
wantErr: false,
}, {
name: "update start",
args: []uint64{0x0000000e},
want: []uint16{0x00000001, 0x00000002, 0x0000000a, 0x0000000b, 0x0000000c, 0x0000000e, 0x0000000f},
wantChanged: true,
wantErr: false,
}, {
name: "combine",
args: []uint64{0x0000000d},
want: []uint16{0x00000001, 0x00000002, 0x0000000a, 0x0000000b, 0x0000000c, 0x0000000d, 0x0000000e, 0x0000000f},
wantChanged: true,
wantErr: false,
}, {
name: "add end",
args: []uint64{0x0000ffff},
want: []uint16{0x00000001, 0x00000002, 0x0000000a, 0x0000000b, 0x0000000c, 0x0000000d, 0x0000000e, 0x0000000f, 0x0000ffff},
wantChanged: true,
wantErr: false,
},
{
name: "overflow container",
args: []uint64{0x00010000},
want: []uint16{0x00000001, 0x00000002, 0x0000000a, 0x0000000b, 0x0000000c, 0x0000000d, 0x0000000e, 0x0000000f, 0x0000ffff},
wantChanged: true,
wantErr: false,
},
}
c, err := tx.Cursor("x")
if err != nil {
t.Fatal(err)
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
changeCount, err := tx.Add("x", tt.args...)
if tt.wantErr && err == nil {
t.Errorf("No Error %v", err)
} else if tt.wantChanged && changeCount == 0 {
t.Errorf("No Change %v", err)
} else if err != nil {
t.Fatal(err)
} else if err := c.First(); err != nil {
t.Fatal(err)
}
if got, want := c.Values(), tt.want; !reflect.DeepEqual(got, want) {
t.Fatalf("Values()=%#v, want %#v", got, want)
}
})
}
t.Run("overflow followup", func(t *testing.T) {
//verify than next container got created and is valid
if err := c.Next(); err != nil { //skip the buffered?
t.Fatal(err)
}
if err := c.Next(); err != nil {
t.Fatal(err)
}
want := []uint16{0}
got := c.Values()
if !reflect.DeepEqual(got, want) {
t.Fatalf("Values()=%#v, want %#v", got, want)
}
if g, w := c.Key(), uint64(1); !reflect.DeepEqual(g, w) {
t.Fatalf("Key()=%#v, want %#v", g, w)
}
})
}
func TestCursor_BitmapBitN(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)
}
bmData := make([]uint64, 1024)
for i := range bmData {
bmData[i] = 0x5555555555555555
}
ct := roaring.NewContainerBitmap(-1, bmData)
err := tx.PutContainer("x", 0, ct)
if err != nil {
t.Fatalf("error writing container: %v", err)
}
// Putting a container to the same slot, which is also a bitmap
// (rather than a BitmapPtr), while the existing cell is a BitmapPtr,
// may not update BitN correctly.
ct, _ = ct.Add(1)
err = tx.PutContainer("x", 0, ct)
if err != nil {
t.Fatalf("rewriting container: %v", err)
}
v, err := tx.Container("x", 0)
if err != nil {
t.Fatalf("getting container: %v", err)
}
c1 := v.N()
v.Repair()
c2 := v.N()
if c1 != c2 {
t.Fatalf("expected count %d, got %d", c2, c1)
}
err = tx.Commit()
assert.NoError(t, err)
}
func TestCursor_RLEConversion(t *testing.T) {
db := MustOpenDB(t)
defer MustCloseDB(t, db)
tx := MustBegin(t, db, true)
defer tx.Rollback()
//setup RLE with full container
if err := tx.CreateBitmap("x"); err != nil {
t.Fatal(err)
}
exp := 0 // expected count of BitN
want := make([]uint16, 0, rbf.ArrayMaxSize)
rb := func() *roaring.Bitmap {
bm := roaring.NewBitmap()
runs := make([]roaring.Interval16, rbf.RLEMaxSize)
x := uint16(1)
for i := range runs {
runs[i] = roaring.Interval16{Start: x, Last: x + 1}
want = append(want, x)
want = append(want, x+1)
x += 3
exp += 2
}
bm.Put(0, roaring.NewContainerRun(runs))
return bm
}()
_, err := tx.AddRoaring("x", rb)
if err != nil {
t.Errorf("Add Roaring Failed %v", err)
}
c, err := tx.Cursor("x")
if err != nil {
t.Fatal(err)
} else if err := c.First(); err != nil {
t.Fatal(err)
}
if c.CurrentPageType() != rbf.ContainerTypeRLE {
t.Fatalf("Should Be RLE but is: %v\n", c.CurrentPageType())
}
exists, err := c.Contains(0x7)
if err != nil {
t.Fatalf("ERR:%v", err)
}
if !exists {
t.Fatalf("Should Contain %v", 0x7)
}
exists, err = c.Contains(0x6)
if err != nil {
t.Fatalf("ERR:%v", err)
}
if exists {
t.Fatalf("Should Not Contain %v", 0x6)
}
c.DebugSlowCheckAllPages()
//i := 0
for x := uint64(65408); x < 65536; x++ {
_, err = tx.Add("x", x)
if err != nil {
panic(err)
}
//vv("on i=%v, x = %v", i, x)
c.DebugSlowCheckAllPages()
}
//add a few bits to create another run
_, err = tx.Add("x",
func() []uint64 {
r := make([]uint64, 0, 128)
for x := uint64(65408); x < 65536; x++ {
r = append(r, x)
want = append(want, uint16(x))
}
return r
}()...)
if err != nil {
t.Fatalf("ERR adding bits: %v\n", err)
}
if err := c.First(); err != nil {
t.Fatal(err)
}
c.DebugSlowCheckAllPages()
//vv("past 2nd check")
got, want := c.Values(), want
if !reflect.DeepEqual(got, want) {
t.Fatalf("Values()=%#v, want %#v", got, want)
}
// Split a run.
// There's a run at the end from 0xff80 - 0xffff (65408 - 65535).
// Split it in half.
removeMe := uint64(65471)
_, err = c.Remove(removeMe)
if err != nil {
t.Fatalf("remove failed")
}
got2 := c.Values()
target := uint16(removeMe)
for _, v := range got2 {
if v == target {
t.Fatalf("removal of %v from rle did not succeed", removeMe)
}
}
c.DebugSlowCheckAllPages()
// add it back
_, err = c.Add(removeMe)
if err != nil {
t.Fatalf("add failed")
}
if !reflect.DeepEqual(got, want) {
t.Fatalf("Values()=%#v, want %#v", got, want)
}
c.DebugSlowCheckAllPages()
}
// test shrinking from bitmap down to array when a bit is removed
func TestCursor_BitmapToArrayConversion(t *testing.T) {
db := MustOpenDB(t)
defer MustCloseDB(t, db)
tx := MustBegin(t, db, true)
defer tx.Rollback()
// Setup a Bitmap with more than ArrayMaxSize, then
// delete bits to down to below ArrayMaxSize, and
// verify the container type got converted to an array
// and that the BitN and ElemN are always correct.
//
if err := tx.CreateBitmap("x"); err != nil {
t.Fatal(err)
}
// start with n = 4084 bits, over ArrayMaxSize
rb := roaring.NewBitmap()
bits := make([]uint64, rbf.BitmapN)
n := 0
for i := range bits {
bits[i] = 15 // ^uint64(0)
n += 4
if n > rbf.ArrayMaxSize {
break
}
}
rb.Put(0, roaring.NewContainerBitmap(n, bits))
// setup to delete random bits down
values := rb.Slice()
valmap := make(map[uint64]bool)
for _, v := range values {
valmap[v] = true
}
_, err := tx.AddRoaring("x", rb)
if err != nil {
t.Errorf("Add Roaring Failed %v", err)
}
c, err := tx.Cursor("x")
if err != nil {
t.Fatal(err)
} else if err := c.First(); err != nil {
t.Fatal(err)
}
if c.CurrentPageType() != rbf.ContainerTypeBitmapPtr {
t.Fatalf("Should Be BitmapPtr but is: %v\n", c.CurrentPageType())
}
exists, err := c.Contains(0x3)
if err != nil {
t.Fatalf("ERR:%v", err)
}
if !exists {
t.Fatalf("Should Contain %v", 0x3)
}
exists, err = c.Contains(0x4)
if err != nil {
t.Fatalf("ERR:%v", err)
}
if exists {
t.Fatalf("Should Not Contain %v", 0x4)
}
c.DebugSlowCheckAllPages()
for len(valmap) > 0 {
// pick a bit to evict
var removeMe uint64
for removeMe = range valmap {
break
}
c.DebugSlowCheckAllPages()
exists, err := c.Contains(removeMe)
if err != nil {
t.Fatalf("ERR:%v", err)
}
if !exists {
t.Fatalf("Should Contain %v", removeMe)
}
_, err = c.Remove(removeMe)
if err != nil {
t.Fatalf("remove failed")
}
//vv("removed %v, have %v left", removeMe, len(valmap))
c.DebugSlowCheckAllPages()
exists, err = c.Contains(removeMe)
if err != nil {
t.Fatalf("ERR:%v", err)
}
if exists {
t.Fatalf("Should NOT Contain %v", removeMe)
}
delete(valmap, removeMe)
n := len(valmap)
if n > rbf.ArrayMaxSize {
if c.CurrentPageType() != rbf.ContainerTypeBitmapPtr {
t.Fatalf("Should Be BitmapPtr but is: %v\n", c.CurrentPageType())
}
} else {
if n > 0 { // no pages if n == 0, so cannot get a CurrentPageType()
if c.CurrentPageType() == rbf.ContainerTypeBitmapPtr {
t.Fatalf("Should NOT Be BitmapPtr but is indeed: %v\n", c.CurrentPageType())
}
}
}
}
// check it is empty
va := c.Values()
if len(va) != 0 {
t.Fatalf("expected empty container, but see %v values left: '%#v'", len(va), va)
}
}
type EasyWalker struct {
tx *rbf.Tx
path strings.Builder
}
func (e *EasyWalker) Visitor(pgno uint32, records []*rbf.RootRecord) {
for _, record := range records {
e.VisitRoot(record.Pgno, record.Name)
rbf.WalkPage(e.tx, record.Pgno, e)
}
}
func (e *EasyWalker) VisitRoot(pgno uint32, name string) {
e.path.WriteString("R")
}
func (e *EasyWalker) Visit(pgno uint32, node rbf.Nodetype) {
switch node {
case rbf.Branch:
e.path.WriteString("B")
case rbf.Leaf:
e.path.WriteString("L")
case rbf.Bitmap:
e.path.WriteString("b")
}
}
func (e *EasyWalker) String() string {
return e.path.String()
}
func TestDumpDot(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)
}
_, err = c.Add(1)
if err != nil {
t.Fatal(err)
}
var b bytes.Buffer
rbf.Dumpdot(tx, 0, " ", &b)
}
func TestCursor_UpdateBranchCells(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)
}
changed, err := c.Add(1)
if err != nil {
t.Fatal(err)
} else if !changed {
t.Fatal("Expected Add Change")
} else if err := c.First(); err != nil {
t.Fatal(err)
} else if got, want := c.Values(), []uint16{uint16(1)}; !reflect.DeepEqual(got, want) {
t.Fatal(err)
}
changed, err = c.Remove(1)
if err != nil {
t.Fatal(err)
} else if !changed {
t.Fatal("Expected Remove Change")
} else if err := c.First(); err != nil && err != io.EOF {
t.Fatal(err)
} else if got, want := c.Values(), ([]uint16)(nil); !reflect.DeepEqual(got, want) {
t.Fatal(err)
}
rb := func() *roaring.Bitmap {
bm := roaring.NewBitmap()
bm.Put(0, roaring.NewContainerRun([]roaring.Interval16{{Start: 1, Last: 2}}))
return bm
}()
_, err = tx.AddRoaring("x", rb)
if err != nil {
t.Fatal(err)
}
changed, err = c.Remove(2)
if changed {
if err := c.First(); err != nil && err != io.EOF {
panic(err)
}
if got, want := c.Values(), []uint16{1}; !reflect.DeepEqual(got, want) {
t.Fatal(err)
}
} else {
t.Fatal("Expected Remove Change")
}
changed, err = c.Remove(1)
if changed {
if err := c.First(); err != nil && err != io.EOF {
panic(err)
}
if got, want := c.Values(), ([]uint16)(nil); !reflect.DeepEqual(got, want) {
t.Fatal(err)
}
} else {
t.Fatal("Expected Remove Change")
}
}
func TestCursor_SplitBranchCells(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)
}
rb := func(key uint64) *roaring.Bitmap {
bm := roaring.NewBitmap()
bits := make([]uint64, rbf.BitmapN)
n := 0
for i := range bits {
bits[i] = ^uint64(0)
n += 64
}
bm.Put(key, roaring.NewContainerBitmap(n, bits))
return bm
}
//634 == offset, 24== size of leafcell with bitmap
// measured should split at 634+(24*314)
numContainers := 314
for i := 0; i < numContainers; i++ { //need to calculate how many will force a split
b := rb(uint64(i))
if _, err := tx.AddRoaring("x", b); err != nil {
panic(err)
}
}
before := &EasyWalker{tx: tx}
rbf.WalkPage(tx, 0, before)
if before.String() != "RL" {
t.Fatalf("Expecting RL (one branch) got %v", before.String())
}
// adding one more container should split it
if _, err := tx.AddRoaring("x", rb(uint64(numContainers))); err != nil {
panic(err)
}
after := &EasyWalker{tx: tx}
rbf.WalkPage(tx, 0, after)
if after.String() != "RBLL" {
t.Fatalf("Expecting RBLL (a branch split) got %v", after.String())
}
//
c, _ := tx.Cursor("x") //added just for dot code coverage
c.Dump("test.dump")
os.Remove("test.dump")
}
func TestCursor_RemoveCells(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)
}
cur, _ := tx.Cursor("x")
rb := func(key uint64) *roaring.Bitmap {
bm := roaring.NewBitmap()
bits := make([]uint64, rbf.BitmapN)
n := 0
for i := range bits {
bits[i] = ^uint64(0)
n += 64
}
bm.Put(key, roaring.NewContainerBitmap(n, bits))
return bm
}
numContainers := 455 //enough containers to cause a split
for i := 0; i < numContainers; i++ {
b := rb(uint64(i))
if _, err := tx.AddRoaring("x", b); err != nil {
panic(err)
}
}
for i := numContainers; i >= 1; i-- {
if _, err := cur.RemoveRoaring(rb(uint64(i))); err != nil {
panic(err)
}
}
if _, err := cur.RemoveRoaring(rb(uint64(0))); err != nil {
panic(err)
}
//f, err := os.OpenFile("before.dot", os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 066)
}
// These aren't test i'm just using to generate graphs to look at structure
func TestCursor_PlayContainer(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)
}
many := func(c *rbf.Cursor, start, count uint64) {
for i := start; i < start+count; i++ {
if _, err := c.Add(i); err != nil {
panic(err)
}
}
}
cur, _ := tx.Cursor("x")
offset := uint64(0)
many(cur, 0, rbf.ArrayMaxSize+offset)
many(cur, 65536, rbf.ArrayMaxSize+offset)
/*
many(cur, 2*65536, rbf.ArrayMaxSize+offset)
many(cur, 3*65536, rbf.ArrayMaxSize) //+offset)
many(cur, 4*65536, rbf.ArrayMaxSize) //+offset)
many(cur, 5*65536, rbf.ArrayMaxSize) //+offset)
many(cur, 6*65536, 10) //+offset)
many(cur, 7*65536, 10) //+offset)
many(cur, 8*65536, rbf.ArrayMaxSize+10) //+offset)
many(cur, 9*65536, rbf.ArrayMaxSize+10) //+offset)
*/
if err := cur.First(); err != nil {
panic(err)
}
}
func TestCursor_OneBitmap(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)
}
rb := func(key uint64) *roaring.Bitmap {
bm := roaring.NewBitmap()
bits := make([]uint64, rbf.BitmapN)
n := 0
for i := range bits {
bits[i] = ^uint64(0)
n += 64
}
bm.Put(key, roaring.NewContainerBitmap(n, bits))
return bm
}
numContainers := 4
for i := 0; i < numContainers; i++ { //need to calculate how many will force a split
b := rb(uint64(i)) // measured at i=454 seems reasonable should occur at Len(branchcells)+header >8192
if _, err := tx.AddRoaring("x", b); err != nil {
panic(err)
}
}
cur, err := tx.Cursor("x")
if err != nil {
panic(err)
}
if err := cur.First(); err != nil {
panic(err)
}
}
func TestCursor_GenerateAll(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)
}
ar := func() *roaring.Bitmap {
bm := roaring.NewBitmap()
bm.Put(11, roaring.NewContainerArray([]uint16{1, 2, 3}))
return bm
}()
if _, err := tx.AddRoaring("x", ar); err != nil {
panic(err)
}
rb := func() *roaring.Bitmap {
bm := roaring.NewBitmap()
bm.Put(1, roaring.NewContainerRun([]roaring.Interval16{{Start: 1, Last: 12}}))
return bm
}()
if _, err := tx.AddRoaring("x", rb); err != nil {
panic(err)
}
bb := func() *roaring.Bitmap {
bm := roaring.NewBitmap()
bm.Put(0, roaring.NewContainerBitmap(makeBitmap([]uint16{75})))
return bm
}()
if _, err := tx.AddRoaring("x", bb); err != nil {
panic(err)
}
if err := tx.CreateBitmap("field/view/"); err != nil {
t.Fatal(err)
}
if _, err := tx.AddRoaring("field/view/", bb); err != nil {
panic(err)
}
}
func TestCursor_PutContainer(t *testing.T) {
t.Run("BitmapToArray", func(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)
}
bmData := make([]uint64, 1024)
for i := range bmData {
bmData[i] = 0x5555555555555555
}
if err := tx.PutContainer("x", 0, roaring.NewContainerBitmap(-1, bmData)); err != nil {
t.Fatal(err)
}
if err := tx.PutContainer("x", 0, roaring.NewContainerArray([]uint16{0})); err != nil {
t.Fatal(err)
}
if err := tx.Commit(); err != nil {
t.Fatal(err)
}
})
t.Run("BitmapToBitmap", func(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)
}
data0 := make([]uint64, 1024)
for i := range data0 {
data0[i] = 0x5555555555555555
}
if err := tx.PutContainer("x", 0, roaring.NewContainerBitmap(-1, data0)); err != nil {
t.Fatal(err)
}
data1 := make([]uint64, 1024)
for i := range data0 {
data1[i] = 0x7777777777777777
}
if err := tx.PutContainer("x", 0, roaring.NewContainerBitmap(-1, data1)); err != nil {
t.Fatal(err)
}
if err := tx.Commit(); err != nil {
t.Fatal(err)
}
})
}