From d46b603cd6a8be06ea385dc08d6b90d8697be26d Mon Sep 17 00:00:00 2001 From: "Jason E. Aten" Date: Thu, 12 Nov 2020 18:19:17 +0000 Subject: [PATCH] rbf: keep ElemN and BitN up to date. - fix a bug in computing leafCell.BitN in a run after a bit Remove - shrink bitmaps on remove - util_test.go has Cursor.DebugSlowCheckAllPages to verify; used by cursor_test.go --- ctl/rbf_page.go | 2 +- go.mod | 2 +- rbf/cursor.go | 164 ++++++---------------- rbf/cursor_test.go | 333 ++++++++++++++++++++++++--------------------- rbf/cursorx.go | 2 +- rbf/dot.go | 2 +- rbf/ingest_test.go | 68 ++++----- rbf/rbf.go | 10 +- rbf/rbf_test.go | 6 + rbf/tx.go | 4 +- rbf/util_test.go | 137 +++++++++++++++++++ 11 files changed, 402 insertions(+), 328 deletions(-) create mode 100644 rbf/util_test.go diff --git a/ctl/rbf_page.go b/ctl/rbf_page.go index 2fa9b70f4..de6a168c1 100644 --- a/ctl/rbf_page.go +++ b/ctl/rbf_page.go @@ -111,7 +111,7 @@ func (cmd *RBFPageCommand) printLeafPage(page *rbf.LeafPage) { fmt.Fprintf(cmd.Stdout, "Type: leaf\n") fmt.Fprintf(cmd.Stdout, "Cells: n=%d\n", len(page.Cells)) for i, cell := range page.Cells { - if cell.Type == "bitmap-ptr" { + if cell.Type == rbf.ContainerTypeBitmapPtr { fmt.Fprintf(cmd.Stdout, "[%d]: key=%d type=%s pgno=%d\n", i, cell.Key, cell.Type, cell.Pgno) } else { fmt.Fprintf(cmd.Stdout, "[%d]: key=%d type=%s values=%v\n", i, cell.Key, cell.Type, cell.Values) diff --git a/go.mod b/go.mod index 9ccdc224f..0690aca19 100644 --- a/go.mod +++ b/go.mod @@ -58,4 +58,4 @@ require ( vitess.io/vitess v3.0.0-rc.3.0.20190602171040-12bfde34629c+incompatible ) -go 1.13 +go 1.14 diff --git a/rbf/cursor.go b/rbf/cursor.go index 189038c2e..eca637b4a 100644 --- a/rbf/cursor.go +++ b/rbf/cursor.go @@ -17,7 +17,6 @@ import ( "encoding/binary" "fmt" "io" - "math/bits" "sort" "unsafe" @@ -39,6 +38,7 @@ type Cursor struct { rle [RLEMaxSize + 1]roaring.Interval16 leafCells [PageSize / 8]leafCell + // stack holds branches stack struct { index int elems [32]stackElem @@ -85,8 +85,9 @@ func runAdd(runs []roaring.Interval16, v uint16) ([]roaring.Interval16, bool) { return runs, true } -// checkRun is only called by Cursor.Add() -func checkRun(runs []roaring.Interval16, bitN int, key uint64) leafCell { +// maybeConvertOversizedRunToBitmap is only called by Cursor.Add(). +// bitN must be the correct new bit count. +func maybeConvertOversizedRunToBitmap(runs []roaring.Interval16, bitN int, key uint64) leafCell { if len(runs) >= RLEMaxSize { //convertToBitmap bitmap := make([]uint64, BitmapN) @@ -120,12 +121,8 @@ func checkRun(runs []roaring.Interval16, bitN int, key uint64) leafCell { words[i] = ^uint64(0) } } - // TODO: take this out once we know bitN matches - n := uint64(0) - for _, v := range bitmap { - n += popcount(v) - } + // note that ElemN should be left 0 for ContainerTypeBitmap. return leafCell{Key: key, BitN: int(bitN), Type: ContainerTypeBitmap, Data: fromArray64(bitmap)} } return leafCell{Key: key, ElemN: len(runs), BitN: int(bitN), Type: ContainerTypeRLE, Data: fromInterval16(runs)} @@ -162,11 +159,10 @@ func (c *Cursor) Add(v uint64) (changed bool, err error) { case ContainerTypeRLE: runs := toInterval16(cell.Data) - //TODO Look at this again with fresh eyes copy(c.rle[:], runs) run, added := runAdd(c.rle[:len(runs)], lo) if added { - leaf := checkRun(run, cell.BitN+1, cell.Key) + leaf := maybeConvertOversizedRunToBitmap(run, cell.BitN+1, cell.Key) return true, c.putLeafCell(leaf) } return false, nil @@ -188,9 +184,9 @@ 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 + // Update with new BitN. + cell.BitN++ + return true, c.putLeafCell(cell) default: return false, fmt.Errorf("rbf.Cursor.Add(): invalid container type: %d", cell.Type) } @@ -267,19 +263,41 @@ func (c *Cursor) Remove(v uint64) (changed bool, err error) { } a := cloneArray64(bm) if a[lo/64]&(1<> 16); err != nil { - return err - } - for { - err := c.Next() - if err == io.EOF { - return nil - } else if err != nil { - return err - } - - cell := c.cell() - key := cell.Key << 16 - if key >= base+ShardWidth { - return nil - } - offset := key - base - switch cell.Type { - case ContainerTypeArray: - for _, v := range toArray16(cell.Data) { - row[(offset+uint64(v))/64] |= 1 << uint64(v%64) - } - case ContainerTypeRLE: - panic("TODO(BBJ): rbf.Bitmap.Union() RLE support") - case ContainerTypeBitmapPtr: - _, bm, err := c.tx.leafCellBitmap(toPgno(cell.Data)) - if err != nil { - return errors.Wrap(err, "union") - } - for i, v := range bm { - row[(offset/64)+uint64(i)] |= v - } - default: - return fmt.Errorf("rbf.Bitmap.Union(): invalid container type: %d", cell.Type) - } - } -} - -// Intersect performs a bitwise AND operation on row and a given row id in the bitmap. -func (c *Cursor) Intersect(rowID uint64, row []uint64) error { - base := rowID * ShardWidth - c.stack.index = 0 - - keyExists := make([]bool, ShardWidth/(1<<16)) - - if _, err := c.Seek(base >> 16); err != nil { - return err - } - for { - err := c.Next() - if err == io.EOF { - break - } else if err != nil { - return err - } - - cell := c.cell() - key := cell.Key << 16 - if key >= base+ShardWidth { - return nil - } - offset := key - base - - keyExists[offset/(1<<16)] = true - - switch cell.Type { - case ContainerTypeArray: - for i, v := range cell.Bitmap(c.tx) { - row[(offset/64)+uint64(i)] &= v - } - case ContainerTypeRLE: - panic("TODO(BBJ): rbf.Bitmap.Intersect() RLE support") - case ContainerTypeBitmapPtr: - _, bm, err := c.tx.leafCellBitmap(toPgno(cell.Data)) - if err != nil { - return errors.Wrap(err, "cursor.Intersect") - } - for i, v := range bm { - row[(offset/64)+uint64(i)] &= v - } - default: - return fmt.Errorf("rbf.Bitmap.Intersect(): invalid container type: %d", cell.Type) - } - } - - // Clear any missing keys. - for i, ok := range keyExists { - if ok { - continue - } - for j := 0; j < (1 << 16); j += 64 { - row[((i*(1<<16))+j)/64] = 0 - } - } - return nil -} - // Values returns the values for the container the cursor is currently pointing to. func (c *Cursor) Values() []uint16 { elem := &c.stack.elems[c.stack.index] @@ -1226,10 +1148,6 @@ func (c *Cursor) AddRoaring(bm *roaring.Bitmap) (changed bool, err error) { return changed, nil } -func popcount(x uint64) uint64 { - return uint64(bits.OnesCount64(x)) -} - func (c *Cursor) RemoveRoaring(bm *roaring.Bitmap) (changed bool, err error) { itr, _ := bm.Containers.Iterator(0) for itr.Next() { diff --git a/rbf/cursor_test.go b/rbf/cursor_test.go index 7582be374..fbb337e35 100644 --- a/rbf/cursor_test.go +++ b/rbf/cursor_test.go @@ -267,162 +267,6 @@ func TestCursor_LastPrev_Quick(t *testing.T) { }) } -func TestCursor_Union(t *testing.T) { - t.Run("OK", 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) - } - - row := make([]uint64, rbf.ShardWidth/64) - - if _, err := tx.Add("x", 1, 3); err != nil { - t.Fatal(err) - } else if _, err := tx.Add("x", rbf.ShardWidth+1, rbf.ShardWidth+2, rbf.ShardWidth+7); err != nil { - t.Fatal(err) - } - - c, err := tx.Cursor("x") - if err != nil { - t.Fatal(err) - } - if err := c.Union(0, row); err != nil { - t.Fatal(err) - } else if row[0] != 0b00001010 { - t.Fatalf("unexpected row[0]: 0b%b", row[0]) - } - - if err := c.Union(1, row); err != nil { - t.Fatal(err) - } else if row[0] != 0b10001110 { - t.Fatalf("unexpected row[0]: 0b%b", row[0]) - } - }) - - t.Run("Quick", func(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) - rows := ToRows(values) - - if err := tx.CreateBitmap("x"); err != nil { - t.Fatal(err) - } - MustAddRandom(t, rand, tx, "x", values...) - - // Iterate over rows and randomly choose another row to union. - for i, row0 := range rows { - row1 := rows[rand.Intn(len(rows))] - - bitmap := row0.Bitmap() - c, err := tx.Cursor("x") - if err != nil { - t.Fatal(err) - } else if err := c.Union(row1.ID, bitmap); err != nil { - return - } - - if got, want := len(rbf.RowValues(bitmap)), len(row0.Union(row1)); got != want { - t.Fatalf("%d. len()=%d, want %d", i, got, want) - } - } - }) - }) -} - -func TestCursor_Intersect(t *testing.T) { - t.Run("OK", func(t *testing.T) { - db := MustOpenDB(t) - defer MustCloseDB(t, db) - tx := MustBegin(t, db, true) - defer tx.Rollback() - - row := make([]uint64, rbf.ShardWidth/64) - - if err := tx.CreateBitmap("x"); err != nil { - t.Fatal(err) - } - - if _, err := tx.Add("x", 1, 3); err != nil { - t.Fatal(err) - } else if _, err := tx.Add("x", rbf.ShardWidth+1, rbf.ShardWidth+2, rbf.ShardWidth+7); err != nil { - t.Fatal(err) - } - - c, err := tx.Cursor("x") - if err != nil { - t.Fatal(err) - } - - if err := c.Union(0, row); err != nil { - t.Fatal(err) - } else if row[0] != 0b00001010 { - t.Fatalf("unexpected row[0]: %#v", row[0]) - } - - if err := c.Intersect(1, row); err != nil { - t.Fatal(err) - } else if row[0] != 0b00000010 { - t.Fatalf("unexpected row[0]: %#v", row[0]) - } - }) - - t.Run("Quick", func(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, rand.Intn(10000)) - rows := ToRows(values) - - if err := tx.CreateBitmap("x"); err != nil { - t.Fatal(err) - } - MustAddRandom(t, rand, tx, "x", values...) - - // Iterate over rows and randomly choose another row to union. - for i, row0 := range rows { - row1 := rows[rand.Intn(len(rows))] - - bitmap := row0.Bitmap() - if c, err := tx.Cursor("x"); err != nil { - t.Fatal(err) - } else if err := c.Intersect(row1.ID, bitmap); err != nil { - t.Fatal(err) - } - - if got, want := len(rbf.RowValues(bitmap)), len(row0.Intersect(row1)); got != want { - t.Fatalf("%d. len()=%d, want %d", i, got, want) - } - } - }) - }) -} - func makeBitmap(bit []uint16) (n int, ret []uint64) { ret = make([]uint64, 1024) for _, v := range bit { @@ -731,6 +575,7 @@ func TestCursor_RLEConversion(t *testing.T) { 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() @@ -741,6 +586,7 @@ func TestCursor_RLEConversion(t *testing.T) { want = append(want, x) want = append(want, x+1) x += 3 + exp += 2 } bm.Put(0, roaring.NewContainerRun(runs)) return bm @@ -775,6 +621,18 @@ func TestCursor_RLEConversion(t *testing.T) { 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 { @@ -792,10 +650,171 @@ func TestCursor_RLEConversion(t *testing.T) { if err := c.First(); err != nil { t.Fatal(err) } - if got, want := c.Values(), want; !reflect.DeepEqual(got, want) { + + 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, where 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 + } + } + //vv("ArrayMaxSize=%v; n = %v", rbf.ArrayMaxSize, n) + 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 { diff --git a/rbf/cursorx.go b/rbf/cursorx.go index 35dd7593a..2d032ba64 100644 --- a/rbf/cursorx.go +++ b/rbf/cursorx.go @@ -150,7 +150,7 @@ func (c *Cursor) Row(shard, rowID uint64) (*roaring.Bitmap, error) { // CurrentPageType returns the type of the container currently pointed to by cursor used in testing // sometimes the cursor needs to be positions prior to this call with First/Last etc. -func (c *Cursor) CurrentPageType() int { +func (c *Cursor) CurrentPageType() ContainerType { cell := c.cell() return cell.Type } diff --git a/rbf/dot.go b/rbf/dot.go index 3a4cd9da7..f1c049874 100644 --- a/rbf/dot.go +++ b/rbf/dot.go @@ -95,7 +95,7 @@ func dumpdot(tx *Tx, pgno uint32, parent string, writer io.Writer) { fmt.Fprintf(writer, "%s[label=\"BRANCH(%d)| n=%d\"]\n %s->%s\n", p, pgno, readCellN(page), parent, p) for i, n := 0, readCellN(page); i < n; i++ { cell := readBranchCell(page, i) - if cell.Flags&ContainerTypeBitmap == 0 { // leaf/branch child page + if cell.Flags&uint32(ContainerTypeBitmap) == 0 { // leaf/branch child page dumpdot(tx, cell.Pgno, p, writer) } else { b := fmt.Sprintf("bm%d", cell.Pgno) diff --git a/rbf/ingest_test.go b/rbf/ingest_test.go index be65d7f3a..881bec444 100644 --- a/rbf/ingest_test.go +++ b/rbf/ingest_test.go @@ -34,35 +34,41 @@ func rbfName(index, field, view string, shard uint64) string { return string(txkey.Prefix(index, field, view, shard)) } +var _ = rbfName // keep linter happy + +/* +// rbtree uses 15% memory and needs half the ingest time +// for our 10K view ingest. +// +// previous master with slice copying instead of rbtree: + +=== RUN TestIngest_lots_of_views +ingest_test.go:141 2020-11-13T03:14:09.778839Z m0.TotalAlloc = 728408 +ingest_test.go:144 2020-11-13T03:14:37.492104Z m1.TotalAlloc = 41,816,617,216 +--- PASS: TestIngest_lots_of_views (27.71s) + +// lots_views with rbtree + +=== RUN TestIngest_lots_of_views +ingest_test.go:141 2020-11-13T03:11:01.540076Z m0.TotalAlloc = 726072 +ingest_test.go:144 2020-11-13T03:11:15.003591Z m1.TotalAlloc = 35,510,273,184 +--- PASS: TestIngest_lots_of_views (13.46s) +*/ func TestIngest_lots_of_views(t *testing.T) { - // skip unless studying perf because is long (15-30 seconds) - //return + // realistic + //nCt := 10000 + + // fast CI + nCt := 10 var m0, m1 runtime.MemStats runtime.ReadMemStats(&m0) - vv("m0.TotalAlloc = %v", m0.TotalAlloc) + //vv("m0.TotalAlloc = %v", m0.TotalAlloc) defer func() { runtime.ReadMemStats(&m1) - vv("m1.TotalAlloc = %v", m1.TotalAlloc) + //vv("m1.TotalAlloc = %v", m1.TotalAlloc) }() - // rbtree uses 15% memory and needs half the ingest time - // for our 10K view ingest. - // - // previous master with slice copying instead of rbtree: - /* - === RUN TestIngest_lots_of_views - ingest_test.go:141 2020-11-13T03:14:09.778839Z m0.TotalAlloc = 728408 - ingest_test.go:144 2020-11-13T03:14:37.492104Z m1.TotalAlloc = 41,816,617,216 - --- PASS: TestIngest_lots_of_views (27.71s) - */ - // lots_views with rbtree - /* - === RUN TestIngest_lots_of_views - ingest_test.go:141 2020-11-13T03:11:01.540076Z m0.TotalAlloc = 726072 - ingest_test.go:144 2020-11-13T03:11:15.003591Z m1.TotalAlloc = 35,510,273,184 - --- PASS: TestIngest_lots_of_views (13.46s) - */ path, err := ioutil.TempDir("", "rbf_ingest_lots_of_views") panicOn(err) @@ -93,15 +99,14 @@ func TestIngest_lots_of_views(t *testing.T) { // put a raw-bitmap container to many views. bits := []uint16{} - for i := 0; i < 1<<16; i++ { - //for i := 0; i < 100; i++ { + //for i := 0; i < 1<<16; i++ { + for i := 0; i < 100; i++ { if i%2 == 0 { bits = append(bits, uint16(i)) } } ct := roaring.NewContainerArray(bits) - nCt := 10000 ckey := uint64(0) shard := ckey / ShardWidth @@ -133,23 +138,10 @@ func TestIngest_lots_of_views(t *testing.T) { sz, err := DiskUse(path, "") panicOn(err) _ = sz - vv("sz in bytes= %v", sz) - + //vv("sz in bytes= %v", sz) db.Close() } -// func (s *rr) last() (r *RootRecord) { -// it := s.tree.Max() -// if it == s.tree.NegativeLimit() { -// return nil -// } -// rec := it.Item().(RootRecord) -// r = &rec -// return -// } - -// var _ = (&rr{}).last // happy linter - func DiskUse(root string, requiredSuffix string) (tot int, err error) { if !DirExists(root) { return -1, fmt.Errorf("listFilesUnderDir error: root directory '%v' not found", root) diff --git a/rbf/rbf.go b/rbf/rbf.go index 33312e63f..29d078538 100644 --- a/rbf/rbf.go +++ b/rbf/rbf.go @@ -66,9 +66,11 @@ const ( MetaPageFlagRollback = 2 ) +type ContainerType int + // Container types. const ( - ContainerTypeNone = iota + ContainerTypeNone ContainerType = iota ContainerTypeArray ContainerTypeRLE ContainerTypeBitmap @@ -76,7 +78,7 @@ const ( ) // ContainerTypeString returns a string representation of the container type. -func ContainerTypeString(typ int) string { +func (typ ContainerType) String() string { switch typ { case ContainerTypeNone: return "none" @@ -289,7 +291,7 @@ func align8(offset int) int { // leafCell represents a leaf cell. type leafCell struct { Key uint64 - Type int // container type + Type ContainerType // ElemN is the number of "things" in Data: // for an array container the number of integers in the array. @@ -477,7 +479,7 @@ 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.Type = ContainerType(*(*uint32)(unsafe.Pointer(&buf[8]))) cell.ElemN = int(*(*uint16)(unsafe.Pointer(&buf[12]))) cell.BitN = int(*(*uint16)(unsafe.Pointer(&buf[14]))) diff --git a/rbf/rbf_test.go b/rbf/rbf_test.go index f628bb04c..082ad4294 100644 --- a/rbf/rbf_test.go +++ b/rbf/rbf_test.go @@ -126,6 +126,8 @@ func MustBegin(tb testing.TB, db *rbf.DB, writable bool) *rbf.Tx { return tx } +var _ = MustAddRandom + // 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() @@ -147,6 +149,8 @@ func GenerateValues(rand *rand.Rand, n int) []uint64 { return a } +var _ = ToRows + // ToRows returns a sorted list of rows from a set of values. func ToRows(values []uint64) []*Row { m := make(map[uint64][]uint64) @@ -163,6 +167,8 @@ func ToRows(values []uint64) []*Row { return a } +var _ = Row{} + type Row struct { ID uint64 Values []uint64 diff --git a/rbf/tx.go b/rbf/tx.go index 13f7690ea..d6721adef 100644 --- a/rbf/tx.go +++ b/rbf/tx.go @@ -1733,7 +1733,7 @@ func (tx *Tx) Pages(pgnos []uint32) ([]Page, error) { for _, cell := range readLeafCells(buf, cells) { other := &LeafCell{ Key: cell.Key, - Type: ContainerTypeString(cell.Type), + Type: cell.Type, } switch cell.Type { @@ -1979,7 +1979,7 @@ type LeafPage struct { // LeafCell represents a leaf cell in the public API. type LeafCell struct { Key uint64 - Type string // container type + Type ContainerType Pgno uint32 // bitmap pointer only Values []uint16 // array & rle containers only } diff --git a/rbf/util_test.go b/rbf/util_test.go new file mode 100644 index 000000000..d5ba63c42 --- /dev/null +++ b/rbf/util_test.go @@ -0,0 +1,137 @@ +// Copyright 2017 Pilosa Corp. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package rbf + +import ( + "fmt" + "github.com/pilosa/pilosa/v2/roaring" + "io" +) + +// util_test adds reusable utilities for testing. +// Here we catch BitN and ElemN mis-settings by +// scanning all data under an rbf-root (logically equivalent +// to a single roaring.Bitmap with multiple rows). + +// verify that BitN and ElemN are correct. +func (c_orig *Cursor) DebugSlowCheckAllPages() { + + // work with a totally new Cursor, so we don't impact our current cursor + // so any test using the cursor isn't disturbed. + c2 := Cursor{tx: c_orig.tx} + c2.stack.elems[0] = c_orig.stack.elems[0] + err := c2.First() + if err != nil { + if err == io.EOF { + // ok, can be empty + return + } else { + panic(err) + } + } + + c2.Dump("debug.dot") + + checkElemNBitN(c2.tx, 0) +} + +// checkElemNBitN recursively writes the tree representation starting from a given page to STDERR. +func checkElemNBitN(tx *Tx, pgno uint32) { + page, err := tx.readPage(pgno) + if err != nil { + panic(err) + } + + if IsMetaPage(page) { + visitor := func(pgno uint32, records []*RootRecord) { + for _, record := range records { + checkElemNBitN(tx, record.Pgno) + } + } + Walk(tx, readMetaRootRecordPageNo(page), visitor) + return + } + + // Handle each type of page + switch typ := readFlags(page); typ { + case PageTypeBranch: + for i, n := 0, readCellN(page); i < n; i++ { + cell := readBranchCell(page, i) + if cell.Flags&uint32(ContainerTypeBitmap) == 0 { // leaf/branch child page + checkElemNBitN(tx, cell.Pgno) + } + // else is a bitmap + } + case PageTypeLeaf: + cellCheckElemNBitN(tx, page) + } +} + +func cellCheckElemNBitN(tx *Tx, b []byte) { + pgno := readPageNo(b) + if pgno == Magic32() { + // skip meta page + return + } + + flags := readFlags(b) + cellN := readCellN(b) + + switch { + case flags&PageTypeLeaf != 0: + for i := 0; i < cellN; i++ { + cell := readLeafCell(b, i) + verifyElemNBitN(tx, cell) + } + } +} +func verifyElemNBitN(tx *Tx, lc leafCell) { + + obsElemN := int32(-1) + obsBitN := int32(-1) + + typ := "" + var c *roaring.Container + switch lc.Type { + case ContainerTypeArray: + typ = "array" + a := toArray16(lc.Data) + c = roaring.NewContainerArray(a) + obsBitN = c.N() + obsElemN = int32(len(a)) + + case ContainerTypeRLE: + typ = "rle" + a := toInterval16(lc.Data) + c = roaring.NewContainerRun(a) + obsBitN = c.N() + obsElemN = int32(len(a)) + + case ContainerTypeBitmap: + panic("should never get an actual bitmap!") + + case ContainerTypeBitmapPtr: + typ = "bitmap_ptr" + _, bm, _ := tx.leafCellBitmap(toPgno(lc.Data)) + c = roaring.NewContainerBitmap(-1, bm) + obsBitN = int32(c.N()) + obsElemN = 0 // by definition, should always see 0 for raw bitmaps's ElemN + } + if lc.BitN != int(obsBitN) { + panic(fmt.Sprintf("lc.BitN(%v) != obsBitN(%v); typ='%v'", lc.BitN, obsBitN, typ)) + } + if lc.ElemN != int(obsElemN) { + panic(fmt.Sprintf("lc.ElemN(%v) != obsElemN(%v); typ='%v'", lc.ElemN, obsElemN, typ)) + } +}