mirror of
https://github.com/featurebasedb/featurebase.git
synced 2026-08-28 10:54:59 +00:00
852 lines
23 KiB
Go
852 lines
23 KiB
Go
// Copyright 2021 Molecula Corp. All rights reserved.
|
|
// Package rbf implements the roaring b-tree file format.
|
|
package rbf
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"math"
|
|
"os"
|
|
"sync"
|
|
"time"
|
|
"unsafe"
|
|
|
|
"github.com/benbjohnson/immutable"
|
|
"github.com/molecula/featurebase/v3/roaring"
|
|
"github.com/molecula/featurebase/v3/shardwidth"
|
|
"github.com/molecula/featurebase/v3/vprint"
|
|
)
|
|
|
|
const (
|
|
// Magic is the first 4 bytes of the RBF file.
|
|
Magic = "\xFFRBF"
|
|
|
|
// PageSize is the fixed size for every database page.
|
|
PageSize = 8192
|
|
|
|
// ShardWidth represents the number of bits per shard.
|
|
ShardWidth = 1 << shardwidth.Exponent
|
|
|
|
// RowValueMask masks the low bits for a row.
|
|
RowValueMask = ShardWidth - 1
|
|
|
|
// ArrayMaxSize represents the maximum size of array containers.
|
|
// This is sligtly less than roaring to accommodate the page header.
|
|
ArrayMaxSize = 4079
|
|
|
|
// RLEMaxSize represents the maximum size of run length encoded containers.
|
|
RLEMaxSize = 2039
|
|
)
|
|
|
|
const maxBranchCellsPerPage = int((PageSize - branchPageHeaderSize) / (branchCellIndexElemSize + unsafe.Sizeof(branchCell{})))
|
|
|
|
// Page types.
|
|
const (
|
|
PageTypeRootRecord = 1
|
|
PageTypeLeaf = 2
|
|
PageTypeBranch = 4
|
|
PageTypeBitmapHeader = 8 // Only used by the WAL for marking next page
|
|
PageTypeBitmap = 16 // Only used internally when walking the b-tree
|
|
)
|
|
|
|
// Meta commit/rollback flags.
|
|
const (
|
|
MetaPageFlagCommit = 1
|
|
MetaPageFlagRollback = 2
|
|
)
|
|
|
|
type ContainerType int
|
|
|
|
// Container types.
|
|
const (
|
|
ContainerTypeNone ContainerType = iota
|
|
ContainerTypeArray
|
|
ContainerTypeRLE
|
|
ContainerTypeBitmap
|
|
ContainerTypeBitmapPtr
|
|
)
|
|
|
|
// ContainerTypeString returns a string representation of the container type.
|
|
func (typ ContainerType) String() string {
|
|
switch typ {
|
|
case ContainerTypeNone:
|
|
return "none"
|
|
case ContainerTypeArray:
|
|
return "array"
|
|
case ContainerTypeRLE:
|
|
return "rle"
|
|
case ContainerTypeBitmap:
|
|
return "bitmap"
|
|
case ContainerTypeBitmapPtr:
|
|
return "bitmap-ptr"
|
|
default:
|
|
return fmt.Sprintf("unknown<%d>", typ)
|
|
}
|
|
}
|
|
|
|
const (
|
|
rootRecordPageHeaderSize = 12
|
|
rootRecordHeaderSize = 4 + 2 // pgno, len(name)
|
|
leafCellHeaderSize = 8 + 4 + 6 // key, type, count
|
|
leafPageHeaderSize = 4 + 4 + 2 // pgno, flags, cell n
|
|
leafCellIndexElemSize = 2
|
|
branchPageHeaderSize = 4 + 4 + 2 // pgno, flags, cell n
|
|
branchCellSize = 8 + 4 + 4 // key, flags, pgno
|
|
branchCellIndexElemSize = 2
|
|
)
|
|
|
|
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")
|
|
ErrTxTooLarge = errors.New("rbf tx too large")
|
|
)
|
|
|
|
// Debug is just a temporary flag used for debugging.
|
|
var Debug bool
|
|
|
|
// Magic32 returns the magic bytes as a big endian encoded uint32.
|
|
func Magic32() uint32 {
|
|
return binary.BigEndian.Uint32([]byte(Magic))
|
|
}
|
|
|
|
// Meta page helpers
|
|
|
|
// IsMetaPage returns true if page is a meta page.
|
|
func IsMetaPage(page []byte) bool {
|
|
return bytes.Equal(readMetaMagic(page), []byte(Magic))
|
|
}
|
|
|
|
func readMetaMagic(page []byte) []byte { return page[0:4] }
|
|
func writeMetaMagic(page []byte) { copy(page, Magic) }
|
|
|
|
func readMetaPageN(page []byte) uint32 { return binary.BigEndian.Uint32(page[8:]) }
|
|
func writeMetaPageN(page []byte, n uint32) { binary.BigEndian.PutUint32(page[8:], n) }
|
|
|
|
func readMetaWALID(page []byte) int64 { return int64(binary.BigEndian.Uint64(page[12:])) }
|
|
func writeMetaWALID(page []byte, walID int64) { binary.BigEndian.PutUint64(page[12:], uint64(walID)) }
|
|
|
|
func readMetaRootRecordPageNo(page []byte) uint32 { return binary.BigEndian.Uint32(page[20:]) }
|
|
func writeMetaRootRecordPageNo(page []byte, pgno uint32) { binary.BigEndian.PutUint32(page[20:], pgno) }
|
|
|
|
func readMetaFreelistPageNo(page []byte) uint32 { return binary.BigEndian.Uint32(page[24:]) }
|
|
func writeMetaFreelistPageNo(page []byte, pgno uint32) { binary.BigEndian.PutUint32(page[24:], pgno) }
|
|
|
|
/* lint
|
|
func readMetaChecksum(page []byte) uint32 {
|
|
return binary.BigEndian.Uint32(page[PageSize-4 : PageSize])
|
|
}
|
|
func writeMetaChecksum(page []byte, chksum uint32) {
|
|
binary.BigEndian.PutUint32(page[PageSize-4:PageSize], chksum)
|
|
}
|
|
*/
|
|
|
|
// Root record page helpers
|
|
|
|
func WalkRootRecordPages(page []byte) uint32 { return binary.BigEndian.Uint32(page[8:]) }
|
|
|
|
func writeRootRecordOverflowPgno(page []byte, pgno uint32) {
|
|
binary.BigEndian.PutUint32(page[8:], pgno)
|
|
}
|
|
|
|
func readRootRecords(page []byte) (records []*RootRecord, err error) {
|
|
for data := page[rootRecordPageHeaderSize:]; ; {
|
|
var rec *RootRecord
|
|
if rec, data, err = ReadRootRecord(data); err != nil {
|
|
return records, err
|
|
} else if rec == nil {
|
|
return records, nil
|
|
}
|
|
records = append(records, rec)
|
|
}
|
|
}
|
|
|
|
// writeRootRecords is only called by tx.go Tx.writeRootRecordPages().
|
|
// We can return io.ErrShortBuffer in err. If we still have records
|
|
// to write that don't fit on page, remain will point to the next
|
|
// record that hasn't yet been written.
|
|
func writeRootRecords(page []byte, itr *immutable.SortedMapIterator[string, uint32]) (err error) {
|
|
data := page[rootRecordPageHeaderSize:]
|
|
|
|
for !itr.Done() {
|
|
name, pgno, _ := itr.Next()
|
|
|
|
data, err = WriteRootRecord(data, &RootRecord{Name: name, Pgno: pgno})
|
|
if err != nil {
|
|
itr.Seek(name)
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Branch & leaf page helpers
|
|
|
|
func readPageNo(page []byte) uint32 { return binary.BigEndian.Uint32(page[0:4]) }
|
|
func writePageNo(page []byte, v uint32) { binary.BigEndian.PutUint32(page[0:4], v) }
|
|
|
|
func readFlags(page []byte) uint32 { return binary.BigEndian.Uint32(page[4:8]) }
|
|
func writeFlags(page []byte, v uint32) { binary.BigEndian.PutUint32(page[4:8], v) }
|
|
|
|
func readCellN(page []byte) int { return int(binary.BigEndian.Uint16(page[8:10])) }
|
|
func writeCellN(page []byte, v int) { binary.BigEndian.PutUint16(page[8:10], uint16(v)) }
|
|
|
|
func readCellOffset(page []byte, i int) int {
|
|
return int(binary.BigEndian.Uint16(page[10+(i*2):]))
|
|
}
|
|
|
|
func writeCellOffset(page []byte, i int, v int) {
|
|
binary.BigEndian.PutUint16(page[10+(i*2):], uint16(v))
|
|
}
|
|
|
|
// readCellEndingOffset returns the last byte position of the i-th cell.
|
|
func readCellEndingOffset(page []byte, i int) int {
|
|
offset := readCellOffset(page, i)
|
|
return offset + len(readLeafCellBytesAtOffset(page, offset))
|
|
}
|
|
|
|
func dataOffset(n int) int {
|
|
return align8(10 + (n * 2))
|
|
}
|
|
|
|
func IsBitmapHeader(page []byte) bool {
|
|
return readFlags(page) == PageTypeBitmapHeader
|
|
}
|
|
|
|
type RootRecord struct {
|
|
Name string
|
|
Pgno uint32
|
|
}
|
|
|
|
// ReadRootRecord reads the page number & name for a root record.
|
|
// If there is not enough space or the pgno is zero then a nil record is returned.
|
|
// Returns the remaining buffer.
|
|
func ReadRootRecord(data []byte) (rec *RootRecord, remaining []byte, err error) {
|
|
// Ensure there is enough space to read the pgno & name length.
|
|
if len(data) < rootRecordHeaderSize {
|
|
return nil, data, nil
|
|
}
|
|
|
|
// Read root page number.
|
|
rec = &RootRecord{}
|
|
rec.Pgno = binary.BigEndian.Uint32(data)
|
|
if rec.Pgno == 0 {
|
|
return nil, data, nil
|
|
}
|
|
data = data[4:]
|
|
|
|
// Read name length.
|
|
sz := int(binary.BigEndian.Uint16(data))
|
|
data = data[2:]
|
|
if len(data) < sz {
|
|
return nil, data, fmt.Errorf("short root record buffer")
|
|
}
|
|
|
|
// Read name and allocate as string on heap.
|
|
rec.Name, data = string(data[:sz]), data[sz:]
|
|
|
|
return rec, data, nil
|
|
}
|
|
|
|
// WriteRootRecord writes a root record with the pgno & name.
|
|
// Returns io.ErrShortBuffer if there is not enough space.
|
|
func WriteRootRecord(data []byte, rec *RootRecord) (remaining []byte, err error) {
|
|
// Ensure record data is valid.
|
|
if rec == nil {
|
|
return data, fmt.Errorf("root record required")
|
|
} else if rec.Name == "" {
|
|
return data, fmt.Errorf("root record name required")
|
|
} else if rec.Pgno == 0 {
|
|
return data, fmt.Errorf("invalid root record pgno: %d", rec.Pgno)
|
|
}
|
|
|
|
// Ensure there is enough space to write the full record.
|
|
if len(data) < rootRecordHeaderSize+len(rec.Name) {
|
|
return data, io.ErrShortBuffer
|
|
}
|
|
|
|
// Write root page number.
|
|
binary.BigEndian.PutUint32(data, rec.Pgno)
|
|
data = data[4:]
|
|
|
|
// Write name length.
|
|
binary.BigEndian.PutUint16(data, uint16(len(rec.Name)))
|
|
data = data[2:]
|
|
|
|
// Write name.
|
|
copy(data, rec.Name)
|
|
data = data[len(rec.Name):]
|
|
|
|
return data, nil
|
|
}
|
|
|
|
func align8(offset int) int {
|
|
if offset%8 == 0 {
|
|
return offset
|
|
}
|
|
return offset + (8 - (offset & 0x7))
|
|
}
|
|
|
|
// leafCell represents a leaf cell.
|
|
type leafCell struct {
|
|
Key uint64
|
|
Type ContainerType
|
|
|
|
// ElemN is the number of "things" in Data:
|
|
// for an array container the number of integers in the array.
|
|
// for an RLE, number of intervals.
|
|
// ElemN is undefined or 0 for ContainerTypeBitmap
|
|
ElemN int
|
|
|
|
BitN int
|
|
Data []byte
|
|
}
|
|
|
|
// Size returns the size of the leaf cell, in bytes.
|
|
func (c *leafCell) Size() int {
|
|
return leafCellHeaderSize + len(c.Data)
|
|
}
|
|
|
|
// Bitmap returns a bitmap representation of the cell data.
|
|
func (c *leafCell) Bitmap(tx *Tx) []uint64 {
|
|
switch c.Type {
|
|
case ContainerTypeArray:
|
|
buf := make([]uint64, PageSize/8)
|
|
for _, v := range toArray16(c.Data) {
|
|
buf[v/64] |= 1 << uint64(v%64)
|
|
}
|
|
return buf
|
|
case ContainerTypeRLE:
|
|
buf := make([]uint64, PageSize/8)
|
|
for _, iv := range toInterval16(c.Data) {
|
|
w1, w2 := iv.Start/64, iv.Last/64
|
|
b1, b2 := iv.Start&63, iv.Last&63
|
|
m1 := (uint64(1) << b1) - 1
|
|
m2 := (((uint64(1) << b2) - 1) << 1) | 1
|
|
if w1 == w2 {
|
|
buf[w1] |= (m2 &^ m1)
|
|
continue
|
|
}
|
|
buf[w2] |= m2
|
|
buf[w1] |= ^m1
|
|
words := buf[w1+1 : w2]
|
|
for i := range words {
|
|
words[i] = ^uint64(0)
|
|
}
|
|
}
|
|
return buf
|
|
case ContainerTypeBitmapPtr:
|
|
_, bm, _ := tx.leafCellBitmap(toPgno(c.Data))
|
|
return bm
|
|
default:
|
|
vprint.PanicOn(fmt.Errorf("invalid container type: %d", c.Type))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Values returns a slice of 16-bit values from a container.
|
|
func (c *leafCell) Values(tx *Tx) []uint16 {
|
|
switch c.Type {
|
|
case ContainerTypeArray:
|
|
return toArray16(c.Data)
|
|
case ContainerTypeRLE:
|
|
a := make([]uint16, c.BitN)
|
|
n := int32(0)
|
|
for _, r := range toInterval16(c.Data) {
|
|
for v := int(r.Start); v <= int(r.Last); v++ {
|
|
a[n] = uint16(v)
|
|
n++
|
|
}
|
|
}
|
|
a = a[:n]
|
|
return a
|
|
case ContainerTypeBitmapPtr:
|
|
_, bm, _ := tx.leafCellBitmap(toPgno(c.Data))
|
|
return bitmapValues(bm)
|
|
case ContainerTypeNone:
|
|
return []uint16{}
|
|
default:
|
|
vprint.PanicOn(fmt.Errorf("invalid container type: %d", c.Type))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func bitmapValues(bm []uint64) []uint16 {
|
|
a := make([]uint16, 0, BitmapN*64)
|
|
for i, v := range bm {
|
|
for j := uint(0); j < 64; j++ {
|
|
if v&(1<<j) != 0 {
|
|
a = append(a, (uint16(i)*64)+uint16(j))
|
|
}
|
|
}
|
|
}
|
|
return a
|
|
}
|
|
|
|
// firstValue the first value from the container.
|
|
func (c *leafCell) firstValue(tx *Tx) uint16 {
|
|
switch c.Type {
|
|
case ContainerTypeArray:
|
|
a := toArray16(c.Data)
|
|
return a[0]
|
|
case ContainerTypeRLE:
|
|
r := toInterval16(c.Data)
|
|
return r[0].Start
|
|
case ContainerTypeBitmapPtr:
|
|
_, slc, err := tx.leafCellBitmap(toPgno(c.Data))
|
|
vprint.PanicOn(err)
|
|
for i, v := range slc {
|
|
for j := uint(0); j < 64; j++ {
|
|
if v&(1<<j) != 0 {
|
|
return (uint16(i) * 64) + uint16(j)
|
|
}
|
|
}
|
|
}
|
|
vprint.PanicOn(fmt.Errorf("rbf.leafCell.firstValue(): no values set in bitmap container: key=%d", c.Key))
|
|
default:
|
|
vprint.PanicOn(fmt.Errorf("invalid container type: %d", c.Type))
|
|
}
|
|
|
|
return 0
|
|
}
|
|
|
|
// helper for lastValue()
|
|
func (c *leafCell) lastValueFromBitmap(a []uint64) uint16 {
|
|
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)
|
|
}
|
|
}
|
|
}
|
|
vprint.PanicOn(fmt.Errorf("rbf.leafCell.lastValueFromBitmap(): no values set in bitmap container: key=%d", c.Key))
|
|
return 0
|
|
}
|
|
|
|
// lastValue the last value from the container.
|
|
func (c *leafCell) lastValue(tx *Tx) 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)
|
|
return c.lastValueFromBitmap(a)
|
|
|
|
case ContainerTypeBitmapPtr:
|
|
_, a, err := tx.leafCellBitmap(toPgno(c.Data))
|
|
vprint.PanicOn(err)
|
|
return c.lastValueFromBitmap(a)
|
|
default:
|
|
vprint.PanicOn(fmt.Errorf("invalid container type: %d", c.Type))
|
|
}
|
|
return 0
|
|
}
|
|
|
|
// countRange returns the bit count within the given range.
|
|
// We have to take int32 rather than uint16 because the interval is [start, end),
|
|
// and otherwise we have no way to ask to count the entire container (the
|
|
// high bit will be missed).
|
|
func (c *leafCell) countRange(tx *Tx, start, end int32) (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), start, end))
|
|
case ContainerTypeRLE:
|
|
return int(roaring.RunCountRange(toInterval16(c.Data), start, end))
|
|
case ContainerTypeBitmap:
|
|
return int(roaring.BitmapCountRange(toArray64(c.Data), start, end))
|
|
case ContainerTypeBitmapPtr:
|
|
_, a, err := tx.leafCellBitmap(toPgno(c.Data))
|
|
vprint.PanicOn(err)
|
|
return int(roaring.BitmapCountRange(a, start, end))
|
|
default:
|
|
vprint.PanicOn(fmt.Errorf("invalid container type: %d", c.Type))
|
|
}
|
|
return
|
|
}
|
|
|
|
func readLeafCellKey(page []byte, i int) uint64 {
|
|
offset := readCellOffset(page, i)
|
|
assert(offset < len(page)) // cell read beyond page size
|
|
return *(*uint64)(unsafe.Pointer(&page[offset]))
|
|
}
|
|
|
|
func readLeafCell(page []byte, i int) leafCell {
|
|
assert(i < readCellN(page)) // cell index exceeds cell count
|
|
offset := readCellOffset(page, i)
|
|
|
|
buf := page[offset:]
|
|
|
|
var cell leafCell
|
|
cell.Key = *(*uint64)(unsafe.Pointer(&buf[0]))
|
|
cell.Type = ContainerType(*(*uint32)(unsafe.Pointer(&buf[8])))
|
|
cell.ElemN = int(*(*uint16)(unsafe.Pointer(&buf[12])))
|
|
cell.BitN = int(*(*uint32)(unsafe.Pointer(&buf[14])))
|
|
|
|
switch cell.Type {
|
|
case ContainerTypeArray:
|
|
cell.Data = buf[leafCellHeaderSize : leafCellHeaderSize+(cell.ElemN*2)]
|
|
case ContainerTypeRLE:
|
|
cell.Data = buf[leafCellHeaderSize : leafCellHeaderSize+(cell.ElemN*4)]
|
|
case ContainerTypeBitmapPtr:
|
|
cell.Data = buf[leafCellHeaderSize : leafCellHeaderSize+4]
|
|
default:
|
|
}
|
|
|
|
return cell
|
|
}
|
|
|
|
func readLeafCellInto(cell *leafCell, page []byte, i int) {
|
|
assert(i < readCellN(page)) // cell index exceeds cell count
|
|
offset := readCellOffset(page, i)
|
|
|
|
buf := page[offset:]
|
|
|
|
cell.Key = *(*uint64)(unsafe.Pointer(&buf[0]))
|
|
cell.Type = ContainerType(*(*uint32)(unsafe.Pointer(&buf[8])))
|
|
cell.ElemN = int(*(*uint16)(unsafe.Pointer(&buf[12])))
|
|
cell.BitN = int(*(*uint32)(unsafe.Pointer(&buf[14])))
|
|
|
|
switch cell.Type {
|
|
case ContainerTypeArray:
|
|
cell.Data = buf[leafCellHeaderSize : leafCellHeaderSize+(cell.ElemN*2)]
|
|
case ContainerTypeRLE:
|
|
cell.Data = buf[leafCellHeaderSize : leafCellHeaderSize+(cell.ElemN*4)]
|
|
case ContainerTypeBitmapPtr:
|
|
cell.Data = buf[leafCellHeaderSize : leafCellHeaderSize+4]
|
|
default:
|
|
cell.Data = nil
|
|
}
|
|
}
|
|
|
|
func readLeafCells(page []byte, buf []leafCell) []leafCell {
|
|
n := readCellN(page)
|
|
cells := buf[:n]
|
|
for i := 0; i < n; i++ {
|
|
cells[i] = readLeafCell(page, i)
|
|
}
|
|
return cells
|
|
}
|
|
|
|
func readLeafCellBytesAtOffset(page []byte, offset int) []byte {
|
|
buf := page[offset:]
|
|
typ := ContainerType(*(*uint32)(unsafe.Pointer(&buf[8])))
|
|
n := int(*(*uint16)(unsafe.Pointer(&buf[12])))
|
|
|
|
switch typ {
|
|
case ContainerTypeArray:
|
|
return buf[:leafCellHeaderSize+(n*2)]
|
|
case ContainerTypeRLE:
|
|
return buf[:leafCellHeaderSize+(n*4)]
|
|
case ContainerTypeBitmapPtr:
|
|
return buf[:leafCellHeaderSize+4]
|
|
default:
|
|
vprint.PanicOn(fmt.Errorf("invalid cell type: %d", typ))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// leafPageSize returns the number of bytes used on a leaf page.
|
|
func leafPageSize(page []byte) int {
|
|
cellN := readCellN(page)
|
|
if cellN == 0 {
|
|
return leafPageHeaderSize
|
|
}
|
|
|
|
// Determine the offset & size of the last element.
|
|
offset := readCellOffset(page, cellN-1)
|
|
return offset + len(readLeafCellBytesAtOffset(page, offset))
|
|
}
|
|
|
|
// leafCellsPageSize returns the total page size required to hold cells.
|
|
func leafCellsPageSize(cells []leafCell) int {
|
|
sz := dataOffset(len(cells))
|
|
for i := range cells {
|
|
sz += align8(cells[i].Size())
|
|
}
|
|
return sz
|
|
}
|
|
|
|
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)
|
|
*(*uint16)(unsafe.Pointer(&page[offset+12])) = uint16(cell.ElemN)
|
|
*(*uint32)(unsafe.Pointer(&page[offset+14])) = uint32(cell.BitN)
|
|
assert(offset+leafCellHeaderSize+len(cell.Data) <= PageSize) // leaf cell write extends beyond page
|
|
copy(page[offset+leafCellHeaderSize:], cell.Data)
|
|
}
|
|
|
|
// branchCell represents a branch cell.
|
|
type branchCell struct {
|
|
LeftKey uint64 // smallest key on ChildPgno
|
|
Flags uint32
|
|
ChildPgno uint32
|
|
}
|
|
|
|
// branchCellsPageSize returns the total page size required to hold cells.
|
|
func branchCellsPageSize(cells []branchCell) int {
|
|
sz := dataOffset(len(cells))
|
|
for range cells {
|
|
sz += align8(branchCellSize)
|
|
}
|
|
return sz
|
|
}
|
|
|
|
func readBranchCellKey(page []byte, i int) uint64 {
|
|
offset := readCellOffset(page, i)
|
|
return *(*uint64)(unsafe.Pointer(&page[offset]))
|
|
}
|
|
|
|
func readBranchCell(page []byte, i int) branchCell {
|
|
assert(i >= 0) // branch cell index must be zero or greater
|
|
assert(i < readCellN(page)) // branch cell index must less than cell count
|
|
|
|
offset := readCellOffset(page, i)
|
|
var cell branchCell
|
|
cell.LeftKey = *(*uint64)(unsafe.Pointer(&page[offset]))
|
|
cell.Flags = *(*uint32)(unsafe.Pointer(&page[offset+8]))
|
|
cell.ChildPgno = *(*uint32)(unsafe.Pointer(&page[offset+12]))
|
|
return cell
|
|
}
|
|
|
|
func readBranchCells(page []byte) []branchCell {
|
|
n := readCellN(page)
|
|
cells := make([]branchCell, n, n+1)
|
|
for i := 0; i < n; i++ {
|
|
cells[i] = readBranchCell(page, i)
|
|
}
|
|
return cells
|
|
}
|
|
|
|
func writeBranchCell(page []byte, i, offset int, cell branchCell) {
|
|
writeCellOffset(page, i, offset)
|
|
*(*uint64)(unsafe.Pointer(&page[offset+0])) = cell.LeftKey
|
|
*(*uint32)(unsafe.Pointer(&page[offset+8])) = uint32(cell.Flags)
|
|
*(*uint32)(unsafe.Pointer(&page[offset+12])) = uint32(cell.ChildPgno)
|
|
}
|
|
|
|
func highbits(v uint64) uint64 { return v >> 16 }
|
|
func lowbits(v uint64) uint16 { return uint16(v & 0xFFFF) }
|
|
|
|
// search implements a binary search similar to sort.Search(), however,
|
|
// it returns the position as well as whether an exact match was made.
|
|
//
|
|
// The return value from f should be -1 for less than, 0 for equal, and 1 for
|
|
// greater than.
|
|
func search(n int, f func(int) int) (index int, exact bool) {
|
|
i, j := 0, n
|
|
for i < j {
|
|
h := int(uint(i+j) >> 1)
|
|
if cmp := f(h); cmp == 0 {
|
|
return h, true
|
|
} else if cmp > 0 {
|
|
i = h + 1
|
|
} else {
|
|
j = h
|
|
}
|
|
}
|
|
return i, false
|
|
}
|
|
|
|
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")
|
|
return
|
|
}
|
|
|
|
flags := readFlags(b)
|
|
cellN := readCellN(b)
|
|
|
|
// NOTE(BBJ): There's no way to tell if a page is a bitmap container with
|
|
// 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 {
|
|
case ContainerTypeArray:
|
|
// fmt.Fprintf(os.Stderr, "[%d]: key=%d type=array n=%d elems=%v\n", i, cell.Key, cell.N, toArray16(cell.Data))
|
|
fmt.Fprintf(writer, "%s[%d]: key=%d type=array BitN=%d \n", indent, i, cell.Key, cell.BitN)
|
|
case ContainerTypeRLE:
|
|
fmt.Fprintf(writer, "%s[%d]: key=%d type=rle BitN=%d\n", indent, i, cell.Key, cell.BitN)
|
|
case ContainerTypeBitmapPtr:
|
|
fmt.Fprintf(writer, "%s[%d]: key=%d type=bitmap BitN=%d\n", indent, i, cell.Key, cell.BitN)
|
|
default:
|
|
fmt.Fprintf(writer, "%s[%d]: key=%d type=unknown<%d> BitN=%d\n", indent, i, cell.Key, cell.Type, cell.BitN)
|
|
}
|
|
}
|
|
case flags&PageTypeBranch != 0:
|
|
fmt.Fprintf(writer, "==BRANCH pgno=%d flags=%d n=%d\n", pgno, flags, cellN)
|
|
for i := 0; i < cellN; i++ {
|
|
cell := readBranchCell(b, i)
|
|
fmt.Fprintf(writer, "[%d]: key=%d flags=%d pgno=%d\n", i, cell.LeftKey, cell.Flags, cell.ChildPgno)
|
|
}
|
|
default:
|
|
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; {
|
|
page, _, err := tx.readPage(pgno)
|
|
if err != nil {
|
|
vprint.PanicOn(err)
|
|
}
|
|
|
|
// Read all records on the page.
|
|
a, err := readRootRecords(page)
|
|
if err != nil {
|
|
vprint.PanicOn(err)
|
|
}
|
|
v(pgno, a)
|
|
// Read next overflow page number.
|
|
pgno = WalkRootRecordPages(page)
|
|
}
|
|
}
|
|
|
|
func assert(condition bool) {
|
|
if !condition {
|
|
vprint.PanicOn(fmt.Errorf("assertion failed"))
|
|
}
|
|
}
|
|
|
|
// RowValues returns a list of integer values from a row bitmap.
|
|
func RowValues(b []uint64) []uint64 {
|
|
a := make([]uint64, 0)
|
|
for i, v := range b {
|
|
for j := uint(0); j < 64; j++ {
|
|
if v&(1<<j) != 0 {
|
|
a = append(a, (uint64(i)*64)+uint64(j))
|
|
}
|
|
}
|
|
}
|
|
return a
|
|
}
|
|
|
|
// func caller(skip int) string {
|
|
// _, file, line, _ := runtime.Caller(skip + 1)
|
|
// return fmt.Sprintf("%s:%d", file, line)
|
|
// }
|
|
|
|
func (db *DB) fsync(f *os.File) error {
|
|
if !db.cfg.FsyncEnabled {
|
|
return nil
|
|
}
|
|
return f.Sync()
|
|
}
|
|
|
|
func (db *DB) fsyncWAL(f *os.File) error {
|
|
if !db.cfg.FsyncEnabled || !db.cfg.FsyncWALEnabled {
|
|
return nil // no sync if either fsync flag is disabled
|
|
}
|
|
return f.Sync()
|
|
}
|
|
|
|
// uint32Hasher implements Hasher for uint32 keys.
|
|
type uint32Hasher struct{}
|
|
|
|
// Hash returns a hash for key.
|
|
func (h *uint32Hasher) Hash(key uint32) uint32 {
|
|
return hashUint64(uint64(key))
|
|
}
|
|
|
|
// hashUint64 returns a 32-bit hash for a 64-bit value.
|
|
func hashUint64(value uint64) uint32 {
|
|
hash := value
|
|
for value > 0xffffffff {
|
|
value /= 0xffffffff
|
|
hash ^= value
|
|
}
|
|
return uint32(hash)
|
|
}
|
|
|
|
// Metric is a simple, internal metric for check duration of operations.
|
|
type Metric struct {
|
|
name string
|
|
interval int // reporting interval
|
|
|
|
mu sync.Mutex
|
|
d time.Duration // total duration
|
|
n int // total count
|
|
}
|
|
|
|
func NewMetric(name string, interval int) Metric {
|
|
assert(interval > 0)
|
|
return Metric{name: name, interval: interval}
|
|
}
|
|
|
|
func (m *Metric) Inc(d time.Duration) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
|
|
m.d += d
|
|
m.n++
|
|
|
|
if m.n != 0 && m.n%m.interval == 0 {
|
|
fmt.Printf("metric:%10s avg=%dns\n", m.name, int(m.d)/m.n)
|
|
}
|
|
}
|
|
|
|
// ErrorList represents a list of errors.
|
|
type ErrorList []error
|
|
|
|
// Err returns the list if it contains errors. Otherwise returns nil.
|
|
func (a ErrorList) Err() error {
|
|
if len(a) > 0 {
|
|
return a
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (a ErrorList) Error() string {
|
|
switch len(a) {
|
|
case 0:
|
|
return "no errors"
|
|
case 1:
|
|
return a[0].Error()
|
|
}
|
|
return fmt.Sprintf("%s (and %d more errors)", a[0], len(a)-1)
|
|
}
|
|
|
|
func (a ErrorList) FullError() string {
|
|
if len(a) == 0 {
|
|
return ""
|
|
}
|
|
|
|
var buf bytes.Buffer
|
|
for _, err := range a {
|
|
fmt.Fprintln(&buf, err)
|
|
}
|
|
return buf.String()
|
|
}
|
|
|
|
// Append appends an error to the list. If err is an ErrorList then all errors are appended.
|
|
func (a *ErrorList) Append(err error) {
|
|
switch err := err.(type) {
|
|
case ErrorList:
|
|
*a = append(*a, err...)
|
|
default:
|
|
*a = append(*a, err)
|
|
}
|
|
}
|