Implement pilosa.Tx for RBF

This commit is contained in:
Ben Johnson 2020-07-29 08:58:06 -06:00
parent 4cdf62ab89
commit 64de208170
22 changed files with 882 additions and 170 deletions

View file

@ -165,6 +165,8 @@ func TestAPI_ImportColumnAttrs(t *testing.T) {
}
func TestAPI_Import(t *testing.T) {
skipForRBF(t)
c := test.MustRunCluster(t, 2,
[]server.CommandOption{
server.OptCommandServerOptions(
@ -276,6 +278,8 @@ func TestAPI_Import(t *testing.T) {
}
func TestAPI_ImportValue(t *testing.T) {
skipForRBF(t)
c := test.MustRunCluster(t, 2,
[]server.CommandOption{
server.OptCommandServerOptions(

View file

@ -276,7 +276,6 @@ func badgerPath(path string) string {
// the existing instance. This insures only one badgerDB
// per bpath in this pilosa node.
func (r *badgerRegistrar) openBadgerDBWrapper(bpath string) (*BadgerDBWrapper, error) {
// now that newTxFactory can call us directly, we might not
// have the -badgerdb suffix.
if !strings.HasSuffix(bpath, "-badgerdb") {

View file

@ -3624,6 +3624,8 @@ func TestExecutor_Execute_FieldValue(t *testing.T) {
// Ensure an all query can be executed.
func TestExecutor_Execute_All(t *testing.T) {
skipForRBF(t)
t.Run("ColumnID", func(t *testing.T) {
c := test.MustRunCluster(t, 1)
defer c.Close()
@ -4026,7 +4028,6 @@ func TestExecutor_Execute_ClearRow(t *testing.T) {
// Ensure a row can be set.
func TestExecutor_Execute_SetRow(t *testing.T) {
t.Run("Set_NewRow", func(t *testing.T) {
c := test.MustRunCluster(t, 1)
defer c.Close()
@ -4385,6 +4386,8 @@ func TestExecutor_Execute_Query_Error(t *testing.T) {
}
func TestExecutor_GroupByStrings(t *testing.T) {
skipForRBF(t)
c := test.MustRunCluster(t, 1)
defer c.Close()
c.CreateField(t, "istring", pilosa.IndexOptions{Keys: true}, "generals", pilosa.OptFieldKeys())
@ -4841,6 +4844,8 @@ func sameStringSlice(x, y []string) bool {
}
func TestExecutor_Execute_GroupBy(t *testing.T) {
skipForRBF(t)
groupByTest := func(t *testing.T, clusterSize int) {
c := test.MustRunCluster(t, 1)
defer c.Close()

View file

@ -3025,13 +3025,15 @@ func (f *fragment) rows(ctx context.Context, tx Tx, start uint64, filters ...row
// unprotectedRows calls rows without grabbing the mutex.
func (f *fragment) unprotectedRows(ctx context.Context, tx Tx, start uint64, filters ...rowFilter) ([]uint64, error) {
rows := make([]uint64, 0)
startKey := rowToKey(start)
i, _, err := tx.ContainerIterator(f.index, f.field, f.view, f.shard, startKey)
if err != nil {
return nil, err
} else if i == nil {
return rows, nil
}
defer i.Close() // must close iterators allocated on a Tx
rows := make([]uint64, 0)
var lastRow uint64 = math.MaxUint64
// Loop over the existing containers.

View file

@ -1786,6 +1786,8 @@ func TestFragment_LRUCache_Persistence(t *testing.T) {
// Ensure a fragment's cache can be persisted between restarts.
func TestFragment_RankCache_Persistence(t *testing.T) {
skipForRBF(t)
index := mustOpenIndex(IndexOptions{})
defer index.Close()
@ -1847,6 +1849,8 @@ func TestFragment_RankCache_Persistence(t *testing.T) {
// Ensure a fragment can be copied to another fragment.
func TestFragment_WriteTo_ReadFrom(t *testing.T) {
skipForRBF(t)
f0, idx := mustOpenFragment("i", "f", viewStandard, 0, "")
_ = idx
defer f0.Clean(t)
@ -4196,6 +4200,8 @@ func TestFragmentRowIterator(t *testing.T) {
})
t.Run("skipped rows wrapped", func(t *testing.T) {
skipForRBF(t)
f, idx := mustOpenFragment("i", "f", "v", 0, CacheTypeRanked)
_ = idx
defer f.Clean(t)
@ -4396,6 +4402,8 @@ func TestFragmentRowIterator_WithTxCommit(t *testing.T) {
row, id, _, wrapped, err := iter.Next()
if err != nil {
t.Fatal(err)
} else if row == nil {
t.Fatal("expected row")
}
if id != i%8 {
t.Errorf("expected row %d but got %d", i%8, id)
@ -5306,6 +5314,7 @@ func check(t *testing.T, tx Tx, f *fragment, exp map[uint64]map[uint64]struct{})
}
func TestImportValueConcurrent(t *testing.T) {
skipForRBF(t)
f, idx := mustOpenBSIFragment("i", "f", viewBSIGroupPrefix+"foo", 0)
switch idx.Txf.TxType() {
@ -5631,3 +5640,9 @@ func TestFragment_Bug_Q2DoubleDelete(t *testing.T) {
t.Fatalf("expected nothing got %v", res)
}
}
func skipForRBF(tb testing.TB) {
if os.Getenv("PILOSA_TXSRC") == "rbf" {
tb.Skip("skip for RBF")
}
}

View file

@ -33,6 +33,7 @@ import (
func TestHolder_Open(t *testing.T) {
skipForBadger := os.Getenv("PILOSA_TXSRC") == "badger"
skipForRBF := os.Getenv("PILOSA_TXSRC") == "rbf"
t.Run("ErrIndexName", func(t *testing.T) {
h := test.MustOpenHolder()
@ -169,6 +170,8 @@ func TestHolder_Open(t *testing.T) {
t.Run("ErrFragmentStoragePermission", func(t *testing.T) {
if skipForBadger {
t.Skip("skipping for badger")
} else if skipForRBF {
t.Skip("skipping for rbf")
}
if os.Geteuid() == 0 {
t.Skip("Skipping permissions test since user is root.")
@ -208,6 +211,8 @@ func TestHolder_Open(t *testing.T) {
t.Run("ErrFragmentStorageCorrupt", func(t *testing.T) {
if skipForBadger {
t.Skip("skipping for badger")
} else if skipForRBF {
t.Skip("skipping for rbf")
}
h := test.MustOpenHolder()
@ -244,6 +249,8 @@ func TestHolder_Open(t *testing.T) {
t.Run("ErrFragmentStorageRecoverable", func(t *testing.T) {
if skipForBadger {
t.Skip("skipping for badger")
} else if skipForRBF {
t.Skip("skipping for rbf")
}
h := test.MustOpenHolder()
@ -410,6 +417,8 @@ func TestHolder_HasData(t *testing.T) {
// Ensure holder can delete an index and its underlying files.
func TestHolder_DeleteIndex(t *testing.T) {
skipForRBF(t)
hldr := test.MustOpenHolder()
defer hldr.Close()
@ -705,6 +714,8 @@ func TestHolderSyncer_TimeQuantum(t *testing.T) {
// Ensure holder can sync integer views with a remote holder.
func TestHolderSyncer_IntField(t *testing.T) {
skipForRBF(t)
t.Run("BasicSync", func(t *testing.T) {
c := test.MustNewCluster(t, 2)
c[0].Config.Cluster.ReplicaN = 2

View file

@ -86,6 +86,8 @@ func forceSnapshotsCheckMapping(t *testing.T) {
// in newGeneration in generation.go. So this is probably useless but it's
// a failure mode we've been bitten by once...
func TestMmapBehavior(t *testing.T) {
skipForRBF(t)
var changed bool
var original uint64
defer func() {

View file

@ -15,6 +15,7 @@
package pilosa_test
import (
"os"
"strings"
"testing"
@ -55,3 +56,9 @@ func TestAddressWithDefaults(t *testing.T) {
}
}
}
func skipForRBF(tb testing.TB) {
if os.Getenv("PILOSA_TXSRC") == "rbf" {
tb.Skip("skip for RBF")
}
}

View file

@ -84,7 +84,7 @@ func runAdd(runs []roaring.Interval16, v uint16) ([]roaring.Interval16, bool) {
}
return runs, true
}
func checkRun(runs []roaring.Interval16, key uint64) leafCell {
func checkRun(runs []roaring.Interval16, bitN int, key uint64) leafCell {
if len(runs) >= RLEMaxSize {
//convertToBitmap
bitmap := make([]uint64, BitmapN)
@ -123,9 +123,9 @@ func checkRun(runs []roaring.Interval16, key uint64) leafCell {
n += popcount(v)
}
return leafCell{Key: key, N: int(n), Type: ContainerTypeBitmap, Data: fromArray64(bitmap)}
return leafCell{Key: key, N: int(n), BitN: int(n), Type: ContainerTypeBitmap, Data: fromArray64(bitmap)}
}
return leafCell{Key: key, N: len(runs), Type: ContainerTypeRLE, Data: fromInterval16(runs)}
return leafCell{Key: key, N: len(runs), BitN: int(bitN + 1), Type: ContainerTypeRLE, Data: fromInterval16(runs)}
}
// Add sets a bit on the underlying bitmap.
@ -136,7 +136,7 @@ func (c *Cursor) Add(v uint64) (changed bool, err error) {
if exact, err := c.Seek(hi); err != nil {
return false, err
} else if !exact {
return true, c.putLeafCell(leafCell{Key: hi, Type: ContainerTypeArray, N: 1, Data: fromArray16([]uint16{lo})})
return true, c.putLeafCell(leafCell{Key: hi, Type: ContainerTypeArray, N: 1, BitN: 1, Data: fromArray16([]uint16{lo})})
}
// If the container exists and bit is not set then update the page.
@ -155,7 +155,7 @@ func (c *Cursor) Add(v uint64) (changed bool, err error) {
copy(other, a[:i])
other[i] = lo
copy(other[i+1:], a[i:])
return true, c.putLeafCell(leafCell{Key: cell.Key, Type: ContainerTypeArray, N: len(other), Data: fromArray16(other)})
return true, c.putLeafCell(leafCell{Key: cell.Key, Type: ContainerTypeArray, N: len(other), BitN: cell.BitN + 1, Data: fromArray16(other)})
case ContainerTypeRLE:
runs := toInterval16(cell.Data)
@ -163,7 +163,7 @@ func (c *Cursor) Add(v uint64) (changed bool, err error) {
copy(c.rle[:], runs)
run, added := runAdd(c.rle[:len(runs)], lo)
if added {
leaf := checkRun(run, cell.Key)
leaf := checkRun(run, cell.BitN, cell.Key)
return true, c.putLeafCell(leaf)
}
return false, nil
@ -185,6 +185,8 @@ func (c *Cursor) Add(v uint64) (changed bool, err error) {
if err := c.tx.writeBitmapPage(pgno, fromArray64(a)); err != nil {
return false, err
}
// TODO(bbj): Update parent cell with new BitN.
return true, nil
default:
return false, fmt.Errorf("rbf.Cursor.Add(): invalid container type: %d", cell.Type)
@ -220,7 +222,7 @@ func (c *Cursor) Remove(v uint64) (changed bool, err error) {
other := make([]uint16, len(a)-1)
copy(other[:i], a[:i])
copy(other[i:], a[i+1:])
return true, c.putLeafCell(leafCell{Key: cell.Key, Type: ContainerTypeArray, N: len(other), Data: fromArray16(other)})
return true, c.putLeafCell(leafCell{Key: cell.Key, Type: ContainerTypeArray, N: len(other), BitN: cell.BitN - 1, Data: fromArray16(other)})
case ContainerTypeRLE:
r := toInterval16(cell.Data)
@ -265,6 +267,8 @@ func (c *Cursor) Remove(v uint64) (changed bool, err error) {
if err := c.tx.writeBitmapPage(pgno, fromArray64(a)); err != nil {
return false, err
}
// TODO(bbj): Update parent cell to decrement BitN.
return true, nil
default:
return false, fmt.Errorf("rbf.Cursor.Add(): invalid container type: %d", cell.Type)
@ -356,7 +360,10 @@ func (c *Cursor) putLeafCell(in leafCell) (err error) {
}
in.Data = fromArray64(a)
cell.Type = ContainerTypeBitmapPtr
bitmapPgno, _ := c.tx.allocate()
bitmapPgno, err := c.tx.allocate()
if err != nil {
return err
}
cell.Data = fromPgno(bitmapPgno)
}
@ -812,6 +819,7 @@ func (c *Cursor) Seek(key uint64) (exact bool, err error) {
if err != nil {
return false, err
}
switch typ := readFlags(buf); typ {
case PageTypeBranch:
n := readCellN(buf)
@ -1089,6 +1097,7 @@ func (c *Cursor) goNextPage() error {
func ConvertToLeafArgs(key uint64, c *roaring.Container) (result leafCell) {
result.Key = key
result.N = int(c.N())
result.BitN = int(c.N())
result.Type = ContainerTypeNone
if c.N() == 0 {
return
@ -1136,7 +1145,7 @@ func (c *Cursor) merge(key uint64, data *roaring.Container) (bool, error) {
if err != nil {
return false, errors.Wrap(err, "cursor.merge")
}
container = roaring.NewContainerBitmap(cell.N, d)
container = roaring.NewContainerBitmap(cell.BitN, d)
case ContainerTypeRLE:
d := toInterval16(cell.Data)
container = roaring.NewContainerRun(d)

View file

@ -31,7 +31,7 @@ func TestCursor_FirstNext(t *testing.T) {
db := MustOpenDB(t)
defer MustCloseDB(t, db)
tx := MustBegin(t, db, true)
defer MustRollback(t, tx)
defer tx.Rollback()
if err := tx.CreateBitmap("x"); err != nil {
t.Fatal(err)
@ -95,7 +95,7 @@ func TestCursor_FirstNext_Quick(t *testing.T) {
db := MustOpenDB(t)
defer MustCloseDB(t, db)
tx := MustBegin(t, db, true)
defer MustRollback(t, tx)
defer tx.Rollback()
// Insert values in random order.
if err := tx.CreateBitmap("x"); err != nil {
@ -153,7 +153,7 @@ func TestCursor_LastPrev(t *testing.T) {
db := MustOpenDB(t)
defer MustCloseDB(t, db)
tx := MustBegin(t, db, true)
defer MustRollback(t, tx)
defer tx.Rollback()
if err := tx.CreateBitmap("x"); err != nil {
t.Fatal(err)
@ -217,7 +217,7 @@ func TestCursor_LastPrev_Quick(t *testing.T) {
db := MustOpenDB(t)
defer MustCloseDB(t, db)
tx := MustBegin(t, db, true)
defer MustRollback(t, tx)
defer tx.Rollback()
// Insert values in random order.
if err := tx.CreateBitmap("x"); err != nil {
@ -276,7 +276,7 @@ func TestCursor_Union(t *testing.T) {
db := MustOpenDB(t)
defer MustCloseDB(t, db)
tx := MustBegin(t, db, true)
defer MustRollback(t, tx)
defer tx.Rollback()
if err := tx.CreateBitmap("x"); err != nil {
t.Fatal(err)
@ -322,7 +322,7 @@ func TestCursor_Union(t *testing.T) {
db := MustOpenDB(t)
defer MustCloseDB(t, db)
tx := MustBegin(t, db, true)
defer MustRollback(t, tx)
defer tx.Rollback()
values := GenerateValues(rand, 10000)
rows := ToRows(values)
@ -356,7 +356,7 @@ func TestCursor_Intersect(t *testing.T) {
db := MustOpenDB(t)
defer MustCloseDB(t, db)
tx := MustBegin(t, db, true)
defer MustRollback(t, tx)
defer tx.Rollback()
row := make([]uint64, rbf.ShardWidth/64)
@ -403,7 +403,7 @@ func TestCursor_Intersect(t *testing.T) {
db := MustOpenDB(t)
defer MustCloseDB(t, db)
tx := MustBegin(t, db, true)
defer MustRollback(t, tx)
defer tx.Rollback()
values := GenerateValues(rand, rand.Intn(10000))
rows := ToRows(values)
@ -447,7 +447,7 @@ func TestCursor_AddRoaring(t *testing.T) {
db := MustOpenDB(t)
defer MustCloseDB(t, db)
tx := MustBegin(t, db, true)
defer MustRollback(t, tx)
defer tx.Rollback()
if err := tx.CreateBitmap("x"); err != nil {
t.Fatal(err)
@ -466,7 +466,7 @@ func TestCursor_AddRoaring(t *testing.T) {
return bm
}(),
wantChanged: false,
wantErr: true},
wantErr: false},
{
name: "initial Array",
fieldview: "x",
@ -614,7 +614,7 @@ func TestCursor_RLETesting(t *testing.T) {
db := MustOpenDB(t)
defer MustCloseDB(t, db)
tx := MustBegin(t, db, true)
defer MustRollback(t, tx)
defer tx.Rollback()
//setup RLE
if err := tx.CreateBitmap("x"); err != nil {
t.Fatal(err)
@ -696,10 +696,10 @@ func TestCursor_RLETesting(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
changed, err := tx.Add("x", tt.args...)
changeCount, err := tx.Add("x", tt.args...)
if tt.wantErr && err == nil {
t.Errorf("No Error %v", err)
} else if tt.wantChanged && !changed {
} else if tt.wantChanged && changeCount == 0 {
t.Errorf("No Change %v", err)
} else if err != nil {
t.Fatal(err)
@ -734,7 +734,7 @@ func TestCursor_RLEConversion(t *testing.T) {
db := MustOpenDB(t)
defer MustCloseDB(t, db)
tx := MustBegin(t, db, true)
defer MustRollback(t, tx)
defer tx.Rollback()
//setup RLE with full container
if err := tx.CreateBitmap("x"); err != nil {
t.Fatal(err)
@ -840,7 +840,7 @@ func TestCursor_UpdateBranchCells(t *testing.T) {
db := MustOpenDB(t)
defer MustCloseDB(t, db)
tx := MustBegin(t, db, true)
defer MustRollback(t, tx)
defer tx.Rollback()
if err := tx.CreateBitmap("x"); err != nil {
t.Fatal(err)
}
@ -914,7 +914,7 @@ func TestCursor_SplitBranchCells(t *testing.T) {
db := MustOpenDB(t)
defer MustCloseDB(t, db)
tx := MustBegin(t, db, true)
defer MustRollback(t, tx)
defer tx.Rollback()
if err := tx.CreateBitmap("x"); err != nil {
t.Fatal(err)
}
@ -964,7 +964,7 @@ func TestCursor_RemoveCells(t *testing.T) {
db := MustOpenDB(t)
defer MustCloseDB(t, db)
tx := MustBegin(t, db, true)
defer MustRollback(t, tx)
defer tx.Rollback()
if err := tx.CreateBitmap("x"); err != nil {
t.Fatal(err)
}
@ -1006,7 +1006,7 @@ func TestCursor_PlayContainer(t *testing.T) {
db := MustOpenDB(t)
defer MustCloseDB(t, db)
tx := MustBegin(t, db, true)
defer MustRollback(t, tx)
defer tx.Rollback()
if err := tx.CreateBitmap("x"); err != nil {
t.Fatal(err)
}
@ -1041,7 +1041,7 @@ func TestCursor_OneBitmap(t *testing.T) {
db := MustOpenDB(t)
defer MustCloseDB(t, db)
tx := MustBegin(t, db, true)
defer MustRollback(t, tx)
defer tx.Rollback()
if err := tx.CreateBitmap("x"); err != nil {
t.Fatal(err)
}
@ -1075,7 +1075,7 @@ func TestCursor_GenerateAll(t *testing.T) {
db := MustOpenDB(t)
defer MustCloseDB(t, db)
tx := MustBegin(t, db, true)
defer MustRollback(t, tx)
defer tx.Rollback()
if err := tx.CreateBitmap("x"); err != nil {
t.Fatal(err)
}

View file

@ -93,10 +93,11 @@ func (c *Cursor) Dump(name string) {
fmt.Fprintf(bufStdout, "\n}")
bufStdout.Flush()
}
func (c *Cursor) Row(rowID uint64) (*roaring.Bitmap, error) {
func (c *Cursor) Row(shard, rowID uint64) (*roaring.Bitmap, error) {
base := rowID * ShardWidth
offset := uint64(c.tx.db.Shard * ShardWidth)
offset := uint64(shard * ShardWidth)
off := highbits(offset)
hi0, hi1 := highbits(base), highbits((rowID+1)*ShardWidth)
c.stack.index = 0

View file

@ -54,27 +54,18 @@ type DB struct {
// The maximum allowed database size. Required by mmap.
MaxSize int64
Shard int
}
// NewDB returns a new instance of DB.
func NewDB(path string) *DB {
return NewDBWithShard(path, 0)
}
func NewDBWithShard(path string, shard int) *DB {
return &DB{
txs: make(map[*Tx]struct{}),
pageMap: immutable.NewMap(&uint32Hasher{}),
Path: path,
MaxSize: DefaultMaxSize,
Shard: shard,
}
}
func (db *DB) DeleteFragment(index, field, view string, shard uint64, frag interface{}) error {
panic("TODO: implement rbf.DB.DeleteFragment")
}
// DataPath returns the path to the data file for the DB.
func (db *DB) DataPath() string {
return filepath.Join(db.Path, "data")
@ -101,7 +92,7 @@ func (db *DB) Open() (err error) {
db.mu.Lock()
defer db.mu.Unlock()
if err := os.MkdirAll(filepath.Dir(db.Path), 0755); err != nil {
if err := os.MkdirAll(db.Path, 0755); err != nil {
return err
} else if db.file, err = os.OpenFile(db.DataPath(), os.O_WRONLY|os.O_CREATE, 0666); err != nil {
return fmt.Errorf("open file: %w", err)
@ -573,7 +564,7 @@ func (db *DB) Begin(writable bool) (_ *Tx, err error) {
// This page is only written at the end of a dirty transaction.
page, err := db.readPage(db.pageMap, 0)
if err != nil {
_ = tx.Rollback()
tx.Rollback()
return nil, err
}
copy(tx.meta[:], page)
@ -617,7 +608,7 @@ func (db *DB) Check() error {
if err != nil {
return err
}
defer func() { _ = tx.Rollback() }()
defer tx.Rollback()
return tx.Check()
}

View file

@ -119,14 +119,13 @@ func TestDB_Recovery(t *testing.T) {
if err != nil {
t.Fatal(err)
}
defer MustRollback(t, tx)
defer tx.Rollback()
if exists, err := tx.Contains("x", uint64(len(a))); exists || err != nil {
t.Fatalf("Contains()=<%v,%#v>", exists, err)
} else if exists, err := tx.Contains("x", uint64(len(a)-1)); !exists || err != nil {
t.Fatalf("Contains()=<%v,%#v>", exists, err)
} else if err := tx.Rollback(); err != nil {
t.Fatal(err)
}
tx.Rollback()
})
}

View file

@ -21,8 +21,11 @@ import (
"errors"
"fmt"
"io"
"math"
"os"
"unsafe"
"github.com/pilosa/pilosa/v2/roaring"
"github.com/pilosa/pilosa/v2/shardwidth"
)
@ -81,6 +84,8 @@ var (
ErrTxClosed = errors.New("transaction closed")
ErrTxNotWritable = errors.New("transaction not writable")
ErrBitmapNameRequired = errors.New("bitmap name required")
ErrBitmapNotFound = errors.New("bitmap not found")
ErrBitmapExists = errors.New("bitmap already exists")
)
// Debug is just a temporary flag used for debugging.
@ -259,6 +264,7 @@ type leafCell struct {
Key uint64
Type int
N int
BitN int
Data []byte
}
@ -361,6 +367,49 @@ func (c *leafCell) firstValue() uint16 {
}
}
// lastValue the last value from the container.
func (c *leafCell) lastValue() uint16 {
switch c.Type {
case ContainerTypeArray:
a := toArray16(c.Data)
return a[len(a)-1]
case ContainerTypeRLE:
r := toInterval16(c.Data)
return r[len(r)-1].Last
case ContainerTypeBitmap:
a := toArray64(c.Data)
for i := len(a) - 1; i >= 0; i-- {
for j := 63; j >= 0; j-- {
if a[i]&(1<<j) != 0 {
return (uint16(i) * 64) + uint16(j)
}
}
}
panic(fmt.Sprintf("rbf.leafCell.firstValue(): no values set in bitmap container: key=%d", c.Key))
default:
panic(fmt.Sprintf("invalid container type: %d", c.Type))
}
}
// countRange returns the bit count within the given range.
func (c *leafCell) countRange(start, end uint16) (n int) {
// If the full range is being queried, simply use the precalculated count.
if start == 0 && end == math.MaxUint16 {
return c.BitN
}
switch c.Type {
case ContainerTypeArray:
return int(roaring.ArrayCountRange(toArray16(c.Data), int32(start), int32(end)))
case ContainerTypeRLE:
return int(roaring.RunCountRange(toInterval16(c.Data), int32(start), int32(end)))
case ContainerTypeBitmap:
return int(roaring.BitmapCountRange(toArray64(c.Data), int32(start), int32(end)))
default:
panic(fmt.Sprintf("invalid container type: %d", c.Type))
}
}
func readLeafCellKey(page []byte, i int) uint64 {
offset := readCellOffset(page, i)
return *(*uint64)(unsafe.Pointer(&page[offset]))
@ -373,7 +422,8 @@ func readLeafCell(page []byte, i int) leafCell {
var cell leafCell
cell.Key = *(*uint64)(unsafe.Pointer(&buf[0]))
cell.Type = int(*(*uint32)(unsafe.Pointer(&buf[8])))
cell.N = int(*(*uint32)(unsafe.Pointer(&buf[12])))
cell.N = int(*(*uint16)(unsafe.Pointer(&buf[12])))
cell.BitN = int(*(*uint16)(unsafe.Pointer(&buf[14])))
switch cell.Type {
case ContainerTypeArray:
@ -410,7 +460,8 @@ func writeLeafCell(page []byte, i, offset int, cell leafCell) {
writeCellOffset(page, i, offset)
*(*uint64)(unsafe.Pointer(&page[offset])) = cell.Key
*(*uint32)(unsafe.Pointer(&page[offset+8])) = uint32(cell.Type)
*(*uint32)(unsafe.Pointer(&page[offset+12])) = uint32(cell.N)
*(*uint16)(unsafe.Pointer(&page[offset+12])) = uint16(cell.N)
*(*uint16)(unsafe.Pointer(&page[offset+14])) = uint16(cell.BitN)
assert(offset+16+len(cell.Data) <= PageSize)
copy(page[offset+16:], cell.Data)
}
@ -486,8 +537,11 @@ func search(n int, f func(int) int) (index int, exact bool) {
return i, false
}
/*
func pagedumpi(b []byte, indent string, writer io.Writer) {
func Pagedump(b []byte, indent string, writer io.Writer) {
if writer == nil {
writer = os.Stderr
}
pgno := readPageNo(b)
if pgno == Magic32() {
fmt.Fprintf(writer, "==META\n")
@ -501,6 +555,7 @@ func pagedumpi(b []byte, indent string, writer io.Writer) {
// the page alone so this will output !PAGE for bitmap pages & invalid pages.
switch {
case flags&PageTypeLeaf != 0:
fmt.Fprintf(writer, "==LEAF pgno=%d flags=%d n=%d\n", pgno, flags, cellN)
for i := 0; i < cellN; i++ {
cell := readLeafCell(b, i)
switch cell.Type {
@ -525,7 +580,6 @@ func pagedumpi(b []byte, indent string, writer io.Writer) {
fmt.Fprintf(writer, "==!PAGE %d flags=%d\n", pgno, flags)
}
}
*/
func Walk(tx *Tx, pgno uint32, v func(uint32, []*RootRecord)) {
for pgno := readMetaRootRecordPageNo(tx.meta[:]); pgno != 0; {
@ -563,3 +617,8 @@ func RowValues(b []uint64) []uint64 {
}
return a
}
// func caller(skip int) string {
// _, file, line, _ := runtime.Caller(skip + 1)
// return fmt.Sprintf("%s:%d", file, line)
// }

View file

@ -119,14 +119,6 @@ func MustBegin(tb testing.TB, db *rbf.DB, writable bool) *rbf.Tx {
return tx
}
// MustRollback rolls back a transaction or fails.
func MustRollback(tb testing.TB, tx *rbf.Tx) {
tb.Helper()
if err := tx.Rollback(); err != nil && err != rbf.ErrTxClosed {
tb.Logf("rollback error: %q", err)
}
}
// MustAddRandom adds values to a bitmap in a random order.
func MustAddRandom(tb testing.TB, rand *rand.Rand, tx *rbf.Tx, name string, values ...uint64) {
tb.Helper()

548
rbf/tx.go
View file

@ -16,7 +16,10 @@ package rbf
import (
"fmt"
"io"
"math"
"sort"
"strings"
"sync"
"github.com/benbjohnson/immutable"
"github.com/pilosa/pilosa/v2/roaring"
@ -24,6 +27,7 @@ import (
// Tx represents a transaction.
type Tx struct {
mu sync.RWMutex
db *DB // parent db
meta [PageSize]byte // copy of current meta page
walID int64 // max WAL ID at start of tx
@ -32,8 +36,16 @@ type Tx struct {
dirty bool // if true, changes have been made
}
// Writable returns true if the transaction can mutate data.
func (tx *Tx) Writable() bool {
return tx.writable
}
// Commit completes the transaction and persists data changes.
func (tx *Tx) Commit() error {
tx.mu.Lock()
defer tx.mu.Unlock()
if tx.db == nil {
return ErrTxClosed
}
@ -57,9 +69,14 @@ func (tx *Tx) Commit() error {
return tx.db.removeTx(tx)
}
func (tx *Tx) Rollback() error {
func (tx *Tx) Rollback() {
tx.mu.Lock()
defer tx.mu.Unlock()
// TODO(bbj): Invalidate DB if rollback fails. Possibly attempt reopen?
if tx.db == nil {
return ErrTxClosed
return
}
// If any pages have been written, ensure we write a new meta page with
@ -67,29 +84,26 @@ func (tx *Tx) Rollback() error {
// discard pages in the transaction during playback of the WAL on open.
if tx.dirty {
if err := tx.writeMetaPage(MetaPageFlagRollback); err != nil {
return err
panic(err)
} else if err := tx.db.SyncWAL(); err != nil {
return err
panic(err)
}
}
if err := tx.db.checkpoint(); err != nil {
return err
}
_ = tx.db.checkpoint() // TODO: Check error
// Disconnect transaction from DB.
err := tx.db.removeTx(tx)
_ = err
/*
if err != nil {
//TODO need to fix this error
}
*/
return nil
_ = tx.db.removeTx(tx) // TODO: Check error
}
// Root returns the root page number for a bitmap. Returns 0 if the bitmap does not exist.
func (tx *Tx) Root(name string) (uint32, error) {
tx.mu.RLock()
defer tx.mu.RUnlock()
return tx.root(name)
}
func (tx *Tx) root(name string) (uint32, error) {
records, err := tx.rootRecords()
if err != nil {
return 0, err
@ -97,14 +111,43 @@ func (tx *Tx) Root(name string) (uint32, error) {
i := sort.Search(len(records), func(i int) bool { return records[i].Name >= name })
if i >= len(records) || records[i].Name != name {
return 0, fmt.Errorf("bitmap not found: %q", name)
return 0, ErrBitmapNotFound
}
return records[i].Pgno, nil
}
// BitmapNames returns a list of all bitmap names.
func (tx *Tx) BitmapNames() ([]string, error) {
tx.mu.RLock()
defer tx.mu.RUnlock()
if tx.db == nil {
return nil, ErrTxClosed
}
// Read list of root records.
records, err := tx.rootRecords()
if err != nil {
return nil, err
}
// Convert to a list of strings.
names := make([]string, len(records))
for i := range records {
names[i] = records[i].Name
}
return names, nil
}
// CreateBitmap creates a new empty bitmap with the given name.
// Returns an error if the bitmap already exists.
func (tx *Tx) CreateBitmap(name string) error {
tx.mu.Lock()
defer tx.mu.Unlock()
return tx.createBitmap(name)
}
func (tx *Tx) createBitmap(name string) error {
if tx.db == nil {
return ErrTxClosed
} else if !tx.writable {
@ -122,7 +165,7 @@ func (tx *Tx) CreateBitmap(name string) error {
// Find btree by name. Exit if already exists.
index := sort.Search(len(records), func(i int) bool { return records[i].Name >= name })
if index < len(records) && records[index].Name == name {
return fmt.Errorf("bitmap already exists: %q", name)
return ErrBitmapExists
}
//fmt.Println("CREATE BITMAP", name, index)
@ -153,6 +196,22 @@ func (tx *Tx) CreateBitmap(name string) error {
return nil
}
// CreateBitmapIfNotExists creates a new empty bitmap with the given name.
// This is a no-op if the bitmap already exists.
func (tx *Tx) CreateBitmapIfNotExists(name string) error {
if err := tx.CreateBitmap(name); err != nil && err != ErrBitmapExists {
return err
}
return nil
}
func (tx *Tx) createBitmapIfNotExists(name string) error {
if err := tx.createBitmap(name); err != nil && err != ErrBitmapExists {
return err
}
return nil
}
/*
func dump(r []*RootRecord) {
for _, i := range r {
@ -165,6 +224,9 @@ func dump(r []*RootRecord) {
// DeleteBitmap removes a bitmap with the given name.
// Returns an error if the bitmap does not exist.
func (tx *Tx) DeleteBitmap(name string) error {
tx.mu.Lock()
defer tx.mu.Unlock()
if tx.db == nil {
return ErrTxClosed
} else if !tx.writable {
@ -200,9 +262,55 @@ func (tx *Tx) DeleteBitmap(name string) error {
return nil
}
// DeleteBitmapsWithPrefix removes all bitmaps with a given prefix.
func (tx *Tx) DeleteBitmapsWithPrefix(prefix string) error {
tx.mu.Lock()
defer tx.mu.Unlock()
if tx.db == nil {
return ErrTxClosed
} else if !tx.writable {
return ErrTxNotWritable
}
// Read list of root records.
records, err := tx.rootRecords()
if err != nil {
return err
}
for i := 0; i < len(records); i++ {
record := records[i]
// Skip bitmaps without matching prefix.
if !strings.HasPrefix(record.Name, prefix) {
continue
}
// Deallocate all pages in the tree.
if err := tx.deallocateTree(record.Pgno); err != nil {
return err
}
// Delete from record list.
records = append(records[:i], records[i+1:]...)
i--
}
// Rewrite record pages.
if err := tx.writeRootRecordPages(records); err != nil {
return fmt.Errorf("write bitmaps: %w", err)
}
return nil
}
// RenameBitmap updates the name of an existing bitmap.
// Returns an error if the bitmap does not exist.
func (tx *Tx) RenameBitmap(oldname, newname string) error {
tx.mu.Lock()
defer tx.mu.Unlock()
if tx.db == nil {
return ErrTxClosed
} else if !tx.writable {
@ -314,78 +422,103 @@ func (tx *Tx) writeRootRecordPages(records []*RootRecord) (err error) {
}
// Add sets a given bit on the bitmap.
func (tx *Tx) Add(name string, a ...uint64) (changed bool, err error) {
func (tx *Tx) Add(name string, a ...uint64) (changeCount int, err error) {
tx.mu.Lock()
defer tx.mu.Unlock()
if tx.db == nil {
return false, ErrTxClosed
return 0, ErrTxClosed
} else if !tx.writable {
return false, ErrTxNotWritable
return 0, ErrTxNotWritable
} else if name == "" {
return false, ErrBitmapNameRequired
return 0, ErrBitmapNameRequired
}
c, err := tx.Cursor(name)
if err := tx.createBitmapIfNotExists(name); err != nil {
return 0, err
}
c, err := tx.cursor(name)
if err != nil {
return false, err
return 0, err
}
for _, v := range a {
if vchanged, err := c.Add(v); err != nil {
return changed, err
return changeCount, err
} else if vchanged {
changed = true
changeCount++
}
}
return changed, nil
return changeCount, nil
}
// Remove unsets a given bit on the bitmap.
func (tx *Tx) Remove(name string, a ...uint64) (changed bool, err error) {
func (tx *Tx) Remove(name string, a ...uint64) (changeCount int, err error) {
tx.mu.Lock()
defer tx.mu.Unlock()
if tx.db == nil {
return false, ErrTxClosed
return 0, ErrTxClosed
} else if !tx.writable {
return false, ErrTxNotWritable
return 0, ErrTxNotWritable
} else if name == "" {
return false, ErrBitmapNameRequired
return 0, ErrBitmapNameRequired
}
c, err := tx.Cursor(name)
c, err := tx.cursor(name)
if err != nil {
return false, err
return 0, err
} else if c == nil {
return 0, nil
}
for _, v := range a {
if vchanged, err := c.Remove(v); err != nil {
return changed, err
return changeCount, err
} else if vchanged {
changed = true
changeCount++
}
}
return changed, nil
return changeCount, nil
}
// Contains returns true if the given bit is set on the bitmap.
func (tx *Tx) Contains(name string, v uint64) (bool, error) {
tx.mu.RLock()
defer tx.mu.RUnlock()
if tx.db == nil {
return false, ErrTxClosed
} else if name == "" {
return false, ErrBitmapNameRequired
}
c, err := tx.Cursor(name)
c, err := tx.cursor(name)
if err != nil {
return false, err
} else if c == nil {
return false, nil
}
return c.Contains(v)
}
// Cursor returns an instance of a cursor this bitmap.
func (tx *Tx) Cursor(name string) (*Cursor, error) {
tx.mu.RLock()
defer tx.mu.RUnlock()
return tx.cursor(name)
}
func (tx *Tx) cursor(name string) (*Cursor, error) {
if tx.db == nil {
return nil, ErrTxClosed
} else if name == "" {
return nil, ErrBitmapNameRequired
}
root, err := tx.Root(name)
if err != nil {
root, err := tx.root(name)
if err == ErrBitmapNotFound {
return nil, nil
} else if err != nil {
return nil, err
}
@ -394,8 +527,109 @@ func (tx *Tx) Cursor(name string) (*Cursor, error) {
return &c, nil
}
// RoaringBitmap returns a bitmap as a Roaring bitmap.
func (tx *Tx) RoaringBitmap(name string) (*roaring.Bitmap, error) {
tx.mu.RLock()
defer tx.mu.RUnlock()
if tx.db == nil {
return nil, ErrTxClosed
} else if name == "" {
return nil, ErrBitmapNameRequired
}
c, err := tx.cursor(name)
if err != nil {
return nil, err
} else if c == nil {
return roaring.NewSliceBitmap(), nil
}
other := roaring.NewSliceBitmap()
if err := c.First(); err == io.EOF {
return other, nil
} else if err != nil {
return nil, err
}
for {
if err := c.Next(); err == io.EOF {
return other, nil
} else if err != nil {
return nil, err
}
cell := c.cell()
other.Containers.Put(cell.Key, toContainer(cell, tx))
}
}
// Container returns a Roaring container by key.
func (tx *Tx) Container(name string, key uint64) (*roaring.Container, error) {
tx.mu.RLock()
defer tx.mu.RUnlock()
if tx.db == nil {
return nil, ErrTxClosed
} else if name == "" {
return nil, ErrBitmapNameRequired
}
c, err := tx.cursor(name)
if err != nil {
return nil, err
} else if c == nil {
return nil, err
} else if exact, err := c.Seek(key); err != nil || !exact {
return nil, err
}
return toContainer(c.cell(), tx), nil
}
// PutContainer inserts a container into a bitmap. Overwrites if key already exists.
func (tx *Tx) PutContainer(name string, key uint64, cont *roaring.Container) error {
tx.mu.Lock()
defer tx.mu.Unlock()
cell := ConvertToLeafArgs(key, cont)
if cell.BitN == 0 {
return nil
}
if err := tx.createBitmapIfNotExists(name); err != nil {
return err
}
c, err := tx.cursor(name)
if err != nil {
return err
} else if _, err := c.Seek(cell.Key); err != nil {
return err
}
return c.putLeafCell(cell)
}
// RemoveContainer removes a container from the bitmap by key.
func (tx *Tx) RemoveContainer(name string, key uint64) error {
tx.mu.Lock()
defer tx.mu.Unlock()
c, err := tx.cursor(name)
if err != nil {
return err
} else if c == nil {
return nil
} else if exact, err := c.Seek(key); err != nil || !exact {
return err
}
return c.deleteLeafCell(key)
}
// Check verifies the integrity of the database.
func (tx *Tx) Check() error {
tx.mu.RLock()
defer tx.mu.RUnlock()
if tx.db == nil {
return ErrTxClosed
}
@ -677,12 +911,20 @@ func (tx *Tx) writeMetaPage(flag uint32) error {
}
func (tx *Tx) AddRoaring(name string, bm *roaring.Bitmap) (changed bool, err error) {
c, err := tx.Cursor(name)
tx.mu.RLock()
defer tx.mu.RUnlock()
if err := tx.createBitmapIfNotExists(name); err != nil {
return false, err
}
c, err := tx.cursor(name)
if err != nil {
return false, err
}
return c.AddRoaring(bm)
}
func (tx *Tx) leafCellBitmap(pgno uint32) (uint32, []uint64, error) {
page, err := tx.readPage(pgno)
if err != nil {
@ -690,3 +932,231 @@ func (tx *Tx) leafCellBitmap(pgno uint32) (uint32, []uint64, error) {
}
return pgno, toArray64(page), err
}
func (tx *Tx) ContainerIterator(name string, key uint64) (citer roaring.ContainerIterator, found bool, err error) {
tx.mu.RLock()
defer tx.mu.RUnlock()
c, err := tx.cursor(name)
if err != nil {
// TODO(bbj): Don't return error if bitmap is simply not found?
return nil, false, err
} else if c == nil {
return nil, false, nil
} else if err := c.First(); err != nil {
return nil, false, err
}
return &containerIterator{cursor: c}, true, nil
}
func (tx *Tx) ForEach(name string, fn func(i uint64) error) error {
return tx.ForEachRange(name, 0, math.MaxUint64, fn)
}
func (tx *Tx) ForEachRange(name string, start, end uint64, fn func(uint64) error) error {
tx.mu.RLock()
defer tx.mu.RUnlock()
c, err := tx.cursor(name)
if err != nil {
return err
} else if c == nil {
return nil
} else if _, err := c.Seek(highbits(start)); err != nil {
return err
}
for {
if err := c.Next(); err == io.EOF {
return nil
} else if err != nil {
return err
}
switch cell := c.cell(); cell.Type {
case ContainerTypeArray:
for _, lo := range toArray16(cell.Data) {
v := cell.Key<<16 | uint64(lo)
if v < start {
continue
} else if v > end {
return nil
} else if err := fn(v); err != nil {
return err
}
}
case ContainerTypeRLE:
for _, r := range toInterval16(cell.Data) {
for lo := int(r.Start); lo <= int(r.Last); lo++ {
v := cell.Key<<16 | uint64(lo)
if v < start {
continue
} else if v > end {
return nil
} else if err := fn(v); err != nil {
return err
}
}
}
case ContainerTypeBitmap:
for i, bits := range toArray64(cell.Data) {
for j := uint(0); j < 64; j++ {
if bits&(1<<j) != 0 {
continue
}
v := cell.Key<<16 | (uint64(i) * 64) | uint64(j)
if v < start {
continue
} else if v > end {
return nil
} else if err := fn(v); err != nil {
return err
}
}
}
default:
panic(fmt.Sprintf("invalid container type: %d", cell.Type))
}
}
}
func (tx *Tx) Count(name string) (uint64, error) {
tx.mu.RLock()
defer tx.mu.RUnlock()
c, err := tx.cursor(name)
if err != nil {
return 0, err
} else if c == nil {
return 0, nil
} else if err := c.First(); err != nil {
return 0, err
}
var n uint64
for {
if err := c.Next(); err == io.EOF {
break
} else if err != nil {
return 0, err
}
n += uint64(c.cell().BitN)
}
return n, nil
}
func (tx *Tx) Max(name string) (uint64, error) {
tx.mu.RLock()
defer tx.mu.RUnlock()
c, err := tx.cursor(name)
if err != nil {
return 0, err
} else if c == nil {
return 0, nil
} else if err := c.Last(); err == io.EOF {
return 0, nil
} else if err != nil {
return 0, err
}
cell := c.cell()
return uint64((cell.Key << 16) | uint64(cell.lastValue())), nil
}
func (tx *Tx) Min(name string) (uint64, bool, error) {
tx.mu.RLock()
defer tx.mu.RUnlock()
c, err := tx.cursor(name)
if err != nil {
return 0, false, err
} else if c == nil {
return 0, false, nil
} else if err := c.First(); err == io.EOF {
return 0, false, nil
} else if err != nil {
return 0, false, err
}
cell := c.cell()
return uint64((cell.Key << 16) | uint64(cell.firstValue())), true, nil
}
func (tx *Tx) UnionInPlace(name string, others ...*roaring.Bitmap) error {
panic("TODO")
}
func (tx *Tx) CountRange(name string, start, end uint64) (uint64, error) {
tx.mu.RLock()
defer tx.mu.RUnlock()
c, err := tx.cursor(name)
if err != nil {
return 0, err
} else if c == nil {
return 0, nil
}
if err := c.First(); err == io.EOF {
return 0, nil
} else if err != nil {
return 0, err
}
var n uint64
for {
if err := c.Next(); err == io.EOF {
break
} else if err != nil {
return 0, err
}
cell := c.cell()
if cell.Key > highbits(end) {
break
}
if cell.Key == highbits(start) {
n += uint64(cell.countRange(lowbits(start), math.MaxUint16))
} else if cell.Key == highbits(end) {
n += uint64(cell.countRange(0, lowbits(end)))
} else {
n += uint64(cell.BitN)
}
}
return n, nil
}
func (tx *Tx) OffsetRange(name string, offset, start, end uint64) (*roaring.Bitmap, error) {
tx.mu.RLock()
defer tx.mu.RUnlock()
b, err := tx.RoaringBitmap(name)
if err != nil {
return nil, err
}
return b.OffsetRange(offset, start, end), nil
}
// containerIterator wraps Cursor to implement roaring.ContainerIterator.
type containerIterator struct {
cursor *Cursor
}
// Close is a no-op. It exists to implement the roaring.ContainerIterator interface.
func (itr *containerIterator) Close() {}
// Next moves the iterator to the next container.
func (itr *containerIterator) Next() bool {
err := itr.cursor.Next()
return err != nil
}
// Value returns the current key & container.
func (itr *containerIterator) Value() (uint64, *roaring.Container) {
cell := itr.cursor.cell()
return cell.Key, toContainer(cell, itr.cursor.tx)
}

View file

@ -29,13 +29,13 @@ func TestTx_CommitRollback(t *testing.T) {
defer MustCloseDB(t, db)
// Create bitmap in transaction but rollback.
if tx, err := db.Begin(true); err != nil {
tx, err := db.Begin(true)
if err != nil {
t.Fatal(err)
} else if err := tx.CreateBitmap("x"); err != nil {
t.Fatal(err)
} else if err := tx.Rollback(); err != nil {
t.Fatal(err)
}
tx.Rollback()
// Create bitmap in transaction again but commit.
if tx, err := db.Begin(true); err != nil {
@ -49,8 +49,8 @@ func TestTx_CommitRollback(t *testing.T) {
// 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.Error() != `bitmap already exists: "x"` {
_ = tx.Rollback()
} 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)
@ -62,13 +62,13 @@ func TestTx_CommitRollback(t *testing.T) {
defer func() { MustCloseDB(t, db) }()
// Create bitmap in transaction but rollback.
if tx, err := db.Begin(true); err != nil {
tx, err := db.Begin(true)
if err != nil {
t.Fatal(err)
} else if err := tx.CreateBitmap("x"); err != nil {
t.Fatal(err)
} else if err := tx.Rollback(); err != nil {
t.Fatal(err)
}
tx.Rollback()
db = MustReopenDB(t, db)
// Create bitmap in transaction again but commit.
@ -84,8 +84,8 @@ func TestTx_CommitRollback(t *testing.T) {
// 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.Error() != `bitmap already exists: "x"` {
_ = tx.Rollback()
} 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)
@ -102,7 +102,7 @@ func TestTx_CommitRollback(t *testing.T) {
tx0 := MustBegin(t, db, true)
go func() {
<-ch0
_ = tx0.Rollback()
tx0.Rollback()
}()
// Start separate write transaction in different goroutine.
@ -134,7 +134,7 @@ func TestTx_Add(t *testing.T) {
db := MustOpenDB(t)
defer MustCloseDB(t, db)
tx := MustBegin(t, db, true)
defer MustRollback(t, tx)
defer tx.Rollback()
if err := tx.CreateBitmap("x"); err != nil {
t.Fatal(err)
@ -167,7 +167,7 @@ func TestTx_DeleteBitmap(t *testing.T) {
db := MustOpenDB(t)
defer MustCloseDB(t, db)
tx := MustBegin(t, db, true)
defer MustRollback(t, tx)
defer tx.Rollback()
// Create bitmap & add value.
if err := tx.CreateBitmap("x"); err != nil {
@ -192,7 +192,7 @@ func TestTx_RenameBitmap(t *testing.T) {
db := MustOpenDB(t)
defer MustCloseDB(t, db)
tx := MustBegin(t, db, true)
defer MustRollback(t, tx)
defer tx.Rollback()
// Create bitmap & add value.
if err := tx.CreateBitmap("x"); err != nil {
@ -226,7 +226,7 @@ func TestTx_Add_Quick(t *testing.T) {
db := MustOpenDB(t)
defer MustCloseDB(t, db)
tx := MustBegin(t, db, true)
defer MustRollback(t, tx)
defer tx.Rollback()
values := GenerateValues(rand, 10000)
if err := tx.CreateBitmap("x"); err != nil {
@ -265,7 +265,7 @@ func TestTx_AddRemove_Quick(t *testing.T) {
db := MustOpenDB(t)
defer MustCloseDB(t, db)
tx := MustBegin(t, db, true)
defer MustRollback(t, tx)
defer tx.Rollback()
values := GenerateValues(rand, 10000)
if err := tx.CreateBitmap("x"); err != nil {
@ -314,7 +314,7 @@ func TestTx_Multiple_CreateBitmap(t *testing.T) {
db := MustOpenDB(t)
defer MustCloseDB(t, db)
tx := MustBegin(t, db, true)
defer MustRollback(t, tx)
defer tx.Rollback()
values := GenerateValues(rand, 2)
if err := tx.CreateBitmap("x/1"); err != nil {
@ -332,7 +332,7 @@ func TestTx_Multiple_CreateBitmap(t *testing.T) {
}
tx1 := MustBegin(t, db, true)
defer func() { _ = tx1.Rollback() }()
defer tx1.Rollback()
if err := tx1.CreateBitmap("x/2"); err != nil {
t.Fatal(err)
@ -353,7 +353,7 @@ func TestTx_CursorCrashArray(t *testing.T) {
db := MustOpenDB(t)
defer MustCloseDB(t, db)
tx := MustBegin(t, db, true)
defer MustRollback(t, tx)
defer tx.Rollback()
if err := tx.CreateBitmap("x"); err != nil {
t.Fatal(err)
}
@ -379,7 +379,7 @@ func TestTx_CursorCrashBitmap(t *testing.T) {
db := MustOpenDB(t)
defer MustCloseDB(t, db)
tx := MustBegin(t, db, true)
defer MustRollback(t, tx)
defer tx.Rollback()
if err := tx.CreateBitmap("x"); err != nil {
t.Fatal(err)
}
@ -418,7 +418,7 @@ func BenchmarkTx_Add(b *testing.B) {
db := MustOpenDB(b)
defer MustCloseDB(b, db)
tx := MustBegin(b, db, true)
defer MustRollback(b, tx)
defer tx.Rollback()
for _, v := range values {
if _, err := tx.Add("x", v); err != nil {
@ -446,7 +446,7 @@ func BenchmarkTx_Contains(b *testing.B) {
db := MustOpenDB(b)
defer MustCloseDB(b, db)
tx := MustBegin(b, db, true)
defer MustRollback(b, tx)
defer tx.Rollback()
b.ResetTimer()
t := time.Now()

View file

@ -2860,20 +2860,19 @@ func (c *Container) countRange(start, end int32) (n int32) {
return 0
}
if c.isArray() {
return c.arrayCountRange(start, end)
return ArrayCountRange(c.array(), start, end)
} else if c.isRun() {
return c.runCountRange(start, end)
return RunCountRange(c.runs(), start, end)
}
return c.bitmapCountRange(start, end)
return BitmapCountRange(c.bitmap(), start, end)
}
func (c *Container) arrayCountRange(start, end int32) (n int32) {
func ArrayCountRange(array []uint16, start, end int32) (n int32) {
if roaringParanoia {
if start > end {
panic(fmt.Sprintf("counting in range but %v > %v", start, end))
}
}
array := c.array()
i := int32(sort.Search(len(array), func(i int) bool { return int32(array[i]) >= start }))
for ; i < int32(len(array)); i++ {
v := int32(array[i])
@ -2885,7 +2884,7 @@ func (c *Container) arrayCountRange(start, end int32) (n int32) {
return n
}
func (c *Container) bitmapCountRange(start, end int32) int32 {
func BitmapCountRange(bitmap []uint64, start, end int32) int32 {
if roaringParanoia {
if start > end {
panic(fmt.Sprintf("counting in range but %v > %v", start, end))
@ -2894,7 +2893,6 @@ func (c *Container) bitmapCountRange(start, end int32) int32 {
var n uint64
i, j := start/64, end/64
// Special case when start and end fall in the same word.
bitmap := c.bitmap()
if i == j {
offi, offj := uint(start%64), uint(64-end%64)
n += popcount((bitmap[i] >> offi) << (offj + offi))
@ -2921,13 +2919,13 @@ func (c *Container) bitmapCountRange(start, end int32) int32 {
return int32(n)
}
func (c *Container) runCountRange(start, end int32) (n int32) {
// RunCountRange returns the ranged bit count for RLE pairs.
func RunCountRange(runs []Interval16, start, end int32) (n int32) {
if roaringParanoia {
if start > end {
panic(fmt.Sprintf("counting in range but %v > %v", start, end))
}
}
runs := c.runs()
for _, iv := range runs {
// iv is before range
if int32(iv.Last) < start {
@ -3837,12 +3835,12 @@ func (c *Container) check() error {
a.Append(fmt.Errorf("array count mismatch: count=%d, n=%d", len(array), c.N()))
}
} else if c.isRun() {
n := c.runCountRange(0, MaxContainerVal+1)
n := RunCountRange(c.runs(), 0, MaxContainerVal+1)
if n != c.N() {
a.Append(fmt.Errorf("run count mismatch: count=%d, n=%d", n, c.N()))
}
} else if c.isBitmap() {
if n := c.bitmapCountRange(0, MaxContainerVal+1); n != c.N() {
if n := BitmapCountRange(c.bitmap(), 0, MaxContainerVal+1); n != c.N() {
a.Append(fmt.Errorf("bitmap count mismatch: count=%d, n=%d", n, c.N()))
}
} else {
@ -4052,7 +4050,7 @@ func intersectionCountRunRun(a, b *Container) (n int32) {
func intersectionCountBitmapRun(a, b *Container) (n int32) {
statsHit("intersectionCount/BitmapRun")
for _, iv := range b.runs() {
n += a.bitmapCountRange(int32(iv.Start), int32(iv.Last)+1)
n += BitmapCountRange(a.bitmap(), int32(iv.Start), int32(iv.Last)+1)
}
return n
}

View file

@ -131,7 +131,7 @@ func TestContainerRunAdd2(t *testing.T) {
func TestRunCountRange(t *testing.T) {
c := NewContainerRun(nil)
cnt := c.runCountRange(2, 9)
cnt := RunCountRange(c.runs(), 2, 9)
if cnt != 0 {
t.Fatalf("should get 0 from empty container, but got: %v", cnt)
}
@ -139,7 +139,7 @@ func TestRunCountRange(t *testing.T) {
c.add(6)
c.add(7)
cnt = c.runCountRange(2, 9)
cnt = RunCountRange(c.runs(), 2, 9)
if cnt != 3 {
t.Fatalf("should get 3 from interval within range, but got: %v", cnt)
}
@ -149,52 +149,52 @@ func TestRunCountRange(t *testing.T) {
c.add(10)
c.add(11)
cnt = c.runCountRange(4, 8)
cnt = RunCountRange(c.runs(), 4, 8)
if cnt != 3 {
t.Fatalf("should get 3 from range overlaps front of interval, but got: %v", cnt)
}
cnt = c.runCountRange(5, 8)
cnt = RunCountRange(c.runs(), 5, 8)
if cnt != 3 {
t.Fatalf("should get 3 from range within interval, but got: %v", cnt)
}
cnt = c.runCountRange(6, 8)
cnt = RunCountRange(c.runs(), 6, 8)
if cnt != 2 {
t.Fatalf("should get 2 from range within interval, but got: %v", cnt)
}
cnt = c.runCountRange(3, 9)
cnt = RunCountRange(c.runs(), 3, 9)
if cnt != 4 {
t.Fatalf("should get 4 from range overlaps front of interval, but got: %v", cnt)
}
cnt = c.runCountRange(9, 14)
cnt = RunCountRange(c.runs(), 9, 14)
if cnt != 3 {
t.Fatalf("should get 3 from range overlaps back of interval, but got: %v", cnt)
}
cnt = c.runCountRange(8, 10)
cnt = RunCountRange(c.runs(), 8, 10)
if cnt != 2 {
t.Fatalf("should get 2 from range within interval, but got: %v", cnt)
}
cnt = c.runCountRange(8, 11)
cnt = RunCountRange(c.runs(), 8, 11)
if cnt != 3 {
t.Fatalf("should get 3 from range within interval, but got: %v", cnt)
}
cnt = c.runCountRange(8, 12)
cnt = RunCountRange(c.runs(), 8, 12)
if cnt != 4 {
t.Fatalf("should get 4 from range overlaps back of interval, but got: %v", cnt)
}
cnt = c.runCountRange(5, 12)
cnt = RunCountRange(c.runs(), 5, 12)
if cnt != 7 {
t.Fatalf("should get 7 from interval within range, but got: %v", cnt)
}
cnt = c.runCountRange(5, 11)
cnt = RunCountRange(c.runs(), 5, 11)
if cnt != 6 {
t.Fatalf("should get 6 from interval equal to range, but got: %v", cnt)
}
@ -203,7 +203,7 @@ func TestRunCountRange(t *testing.T) {
c.add(19)
c.add(18)
cnt = c.runCountRange(1, 22)
cnt = RunCountRange(c.runs(), 1, 22)
if cnt != 10 {
t.Fatalf("should get 10 from multiple ranges in interval, but got: %v", cnt)
}
@ -211,7 +211,7 @@ func TestRunCountRange(t *testing.T) {
c.add(13)
c.add(14)
cnt = c.runCountRange(6, 18)
cnt = RunCountRange(c.runs(), 6, 18)
if cnt != 9 {
t.Fatalf("should get 9 from multiple ranges overlapping both sides, but got: %v", cnt)
}
@ -263,7 +263,7 @@ func TestBitmapCountRange(t *testing.T) {
for i, test := range tests {
c.setBitmap(test.bitmap[:])
if ret := c.bitmapCountRange(test.start, test.end); ret != test.exp {
if ret := BitmapCountRange(c.bitmap(), test.start, test.end); ret != test.exp {
t.Fatalf("test #%v count of %v from %v to %v should be %v but got %v", i, test.bitmap, test.start, test.end, test.exp, ret)
}
}

146
tx.go
View file

@ -21,8 +21,10 @@ import (
"os"
"path/filepath"
"strconv"
"strings"
"sync"
"github.com/pilosa/pilosa/v2/rbf"
"github.com/pilosa/pilosa/v2/roaring"
"github.com/pkg/errors"
)
@ -872,3 +874,147 @@ func (tx *RoaringTx) RoaringBitmapReader(index, field, view string, shard uint64
r = file
return
}
type RBFTx struct {
tx *rbf.Tx
}
func (tx *RBFTx) Type() string {
return RBFTxn
}
func (tx *RBFTx) Rollback() {
tx.tx.Rollback()
}
func (tx *RBFTx) Commit() error {
return tx.tx.Commit()
}
func (tx *RBFTx) RoaringBitmap(index, field, view string, shard uint64) (*roaring.Bitmap, error) {
return tx.tx.RoaringBitmap(rbfName(field, view, shard))
}
func (tx *RBFTx) Container(index, field, view string, shard uint64, key uint64) (*roaring.Container, error) {
return tx.tx.Container(rbfName(field, view, shard), key)
}
func (tx *RBFTx) PutContainer(index, field, view string, shard uint64, key uint64, c *roaring.Container) error {
return tx.tx.PutContainer(rbfName(field, view, shard), key, c)
}
func (tx *RBFTx) RemoveContainer(index, field, view string, shard uint64, key uint64) error {
return tx.tx.RemoveContainer(rbfName(field, view, shard), key)
}
func (tx *RBFTx) Add(index, field, view string, shard uint64, batched bool, a ...uint64) (changeCount int, err error) {
return tx.tx.Add(rbfName(field, view, shard), a...)
}
func (tx *RBFTx) Remove(index, field, view string, shard uint64, a ...uint64) (changeCount int, err error) {
return tx.tx.Remove(rbfName(field, view, shard), a...)
}
func (tx *RBFTx) Contains(index, field, view string, shard uint64, v uint64) (exists bool, err error) {
return tx.tx.Contains(rbfName(field, view, shard), v)
}
func (tx *RBFTx) ContainerIterator(index, field, view string, shard uint64, key uint64) (citer roaring.ContainerIterator, found bool, err error) {
return tx.tx.ContainerIterator(rbfName(field, view, shard), key)
}
func (tx *RBFTx) ForEach(index, field, view string, shard uint64, fn func(i uint64) error) error {
return tx.tx.ForEach(rbfName(field, view, shard), fn)
}
func (tx *RBFTx) ForEachRange(index, field, view string, shard uint64, start, end uint64, fn func(uint64) error) error {
return tx.tx.ForEachRange(rbfName(field, view, shard), start, end, fn)
}
func (tx *RBFTx) Count(index, field, view string, shard uint64) (uint64, error) {
return tx.tx.Count(rbfName(field, view, shard))
}
func (tx *RBFTx) Max(index, field, view string, shard uint64) (uint64, error) {
return tx.tx.Max(rbfName(field, view, shard))
}
func (tx *RBFTx) Min(index, field, view string, shard uint64) (uint64, bool, error) {
return tx.tx.Min(rbfName(field, view, shard))
}
func (tx *RBFTx) UnionInPlace(index, field, view string, shard uint64, others ...*roaring.Bitmap) error {
return tx.tx.UnionInPlace(rbfName(field, view, shard), others...)
}
func (tx *RBFTx) CountRange(index, field, view string, shard uint64, start, end uint64) (uint64, error) {
return tx.tx.CountRange(rbfName(field, view, shard), start, end)
}
func (tx *RBFTx) OffsetRange(index, field, view string, shard uint64, offset, start, end uint64) (*roaring.Bitmap, error) {
return tx.tx.OffsetRange(rbfName(field, view, shard), offset, start, end)
}
func (tx *RBFTx) IncrementOpN(index, field, view string, shard uint64, changedN int) {}
func (tx *RBFTx) ImportRoaringBits(index, field, view string, shard uint64, rit roaring.RoaringIterator, clear bool, log bool, rowSize uint64, data []byte) (changed int, rowSet map[uint64]int, err error) {
// TODO: Implement RBFTX.ImportRoaringBits"
return 0, make(map[uint64]int), nil
}
func (tx *RBFTx) RoaringBitmapReader(index, field, view string, shard uint64, fragmentPathForRoaring string) (r io.ReadCloser, sz int64, err error) {
panic("TODO: Implement RBFTx.RoaringBitmapReader()")
}
func (tx *RBFTx) SliceOfShards(index, field, view, optionalViewPath string) (sliceOfShards []uint64, err error) {
prefix := rbfFieldViewPrefix(field, view)
names, err := tx.tx.BitmapNames()
if err != nil {
return nil, err
}
// Iterate over shard names and collect shards from matching field/view prefix.
for _, name := range names {
if !strings.HasPrefix(name, prefix) {
continue
}
s := strings.TrimPrefix(name, prefix)
shard, err := strconv.ParseUint(s, 10, 64)
if err != nil {
return nil, errors.Wrap(err, "parse shard id from rbf key")
}
sliceOfShards = append(sliceOfShards, shard)
}
return sliceOfShards, nil
}
func (tx *RBFTx) NewTxIterator(index, field, view string, shard uint64) *roaring.Iterator {
b, err := tx.RoaringBitmap(index, field, view, shard)
panicOn(err)
return b.Iterator()
}
func (tx *RBFTx) Pointer() string {
return fmt.Sprintf("%p", tx)
}
// Readonly is true if the transaction is not read-and-write, but only doing reads.
func (tx *RBFTx) Readonly() bool {
return !tx.tx.Writable()
}
func (tx *RBFTx) UseRowCache() bool {
return false
}
// rbfName returns a NULL-separated key used for identifying bitmap maps in RBF.
func rbfName(field, view string, shard uint64) string {
return fmt.Sprintf("%s\x00%s\x00%d", field, view, shard)
}
// rbfFieldViewPrefix returns a NULL-separated prefix for keys in RBF.
func rbfFieldViewPrefix(field, view string) string {
return fmt.Sprintf("%s\x00%s\x00", field, view)
}

View file

@ -81,6 +81,8 @@ func (f *TxFactory) Store() TxStore {
// case blueGreenRoaringBadger:
}
panic(fmt.Sprintf("unknown f.typeOfTx type: '%v'", f.typeOfTx))
=======
>>>>>>> Implement pilosa.Tx for RBF
}
*/
@ -168,6 +170,7 @@ func NewTxFactory(txsrc string, dir, name string) (f *TxFactory, err error) {
typeOfTx: ty,
roaringDB: NewRoaringStore(),
}
switch ty {
case badgerTxn, blueGreenBadgerRoaring, blueGreenRoaringBadger, blueGreenBadgerRBF, blueGreenRBFBadger:
@ -193,10 +196,9 @@ func NewTxFactory(txsrc string, dir, name string) (f *TxFactory, err error) {
switch ty {
case rbfTxn, blueGreenRBFRoaring, blueGreenRoaringRBF, blueGreenBadgerRBF, blueGreenRBFBadger:
path := dir + sep + name + ".rbf"
f.rbfDB = rbf.NewDB(path)
f.rbfDB = rbf.NewDB(filepath.Join(dir, "db.rbf"))
if err := f.rbfDB.Open(); err != nil {
return nil, errors.Wrap(err, fmt.Sprintf("cannot open rbf db. path='%v'", path))
return nil, errors.Wrap(err, "cannot open rbf db")
}
}
@ -240,8 +242,16 @@ func (f *TxFactory) DeleteFragmentFromStore(index, field, view string, shard uin
case badgerTxn:
return f.badgerDB.DeleteFragment(index, field, view, shard, frag)
case rbfTxn:
//return f.rbfDB.DeleteFragment(index, field, view, shard, frag)
return nil
tx, err := f.rbfDB.Begin(true)
if err != nil {
return err
}
defer tx.Rollback()
if err := tx.DeleteBitmapsWithPrefix(rbfFieldViewPrefix(field, view)); err != nil {
return err
}
return tx.Commit()
case blueGreenBadgerRoaring:
_ = f.badgerDB.DeleteFragment(index, field, view, shard, frag)
return f.roaringDB.DeleteFragment(index, field, view, shard, frag)
@ -263,9 +273,7 @@ func (f *TxFactory) CloseIndex(idx *Index) error {
//return f.badgerDB.Close()
return nil
case rbfTxn:
// for same reason as above may not be able to close here.
//return f.rbfDB.Close()
return nil
return f.rbfDB.Close()
case blueGreenBadgerRoaring:
return nil
case blueGreenRoaringBadger:
@ -275,7 +283,6 @@ func (f *TxFactory) CloseIndex(idx *Index) error {
}
func (f *TxFactory) NewTx(o Txo) Tx {
indexName := ""
if o.Index != nil {
indexName = o.Index.name
@ -288,14 +295,11 @@ func (f *TxFactory) NewTx(o Txo) Tx {
btx := f.badgerDB.NewBadgerTx(o.Write, indexName)
return btx
case rbfTxn:
panic("todo rbfTxn creation")
/*
rbftx, err := f.rbfDB.Begin(o.Write)
if err != nil {
errors.Wrap(err, "rbfDB.Begin transaction errored")
}
return rbftx
*/
tx, err := f.rbfDB.Begin(o.Write)
if err != nil {
panic(err) // TODO: Add error return on NewTx()
}
return &RBFTx{tx: tx}
case blueGreenBadgerRoaring:
btx := f.badgerDB.NewBadgerTx(o.Write, indexName)
rtx := &RoaringTx{write: o.Write, Field: o.Field, Index: o.Index, fragment: o.Fragment}
@ -379,7 +383,6 @@ func (idx *Index) StringifiedBadgerKeys(optionalUseThisTx Tx) string {
// index directory, not including the name of the index itself.
// The path should not start with the path separator sep ('/' or '\\') rune.
func fragmentSpecFromRoaringPath(path string) (field, view string, shard uint64, err error) {
if len(path) == 0 {
err = fmt.Errorf("fragmentSpecFromRoaringPath error: path '%v' too short", path)
return
@ -408,7 +411,6 @@ func fragmentSpecFromRoaringPath(path string) (field, view string, shard uint64,
}
func (idx *Index) StringifiedRoaringKeys() (r string) {
paths, err := listFilesUnderDir(idx.path, false, "", true)
panicOn(err)
index := idx.name

View file

@ -63,7 +63,7 @@ func (rbc *RBFConverter) Convert(index, field, view string, shard uint64, rb *ro
if err != nil {
return err
}
defer func() { _ = tx.Rollback() }()
defer tx.Rollback()
name := fmt.Sprintf("%s/%s", field, view)
err = tx.CreateBitmap(name)