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

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

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

1423 lines
46 KiB
Go

// Copyright 2021 Molecula Corp. All rights reserved.
package roaring
import (
"fmt"
"math"
"github.com/pkg/errors"
"github.com/molecula/featurebase/v3/shardwidth"
)
// We want BitmapScanner to be accessible from both the pilosa package, and
// the rbf package. Pilosa imports rbf, so rbf can't import pilosa, but they
// both import roaring, and this package is closely tied to roaring structures
// like Containers and the key/container mapping, so it mostly makes sense for
// this to be here.
//
// Unfortunately, this really needs to be capable of being row-aware, which
// means it needs access to the shard width stuff, which roaring otherwise
// studiously avoids knowing about.
const (
rowExponent = (shardwidth.Exponent - 16) // for instance, 20-16 = 4
rowWidth = 1 << rowExponent // containers per row, for instance 1<<4 = 16
keyMask = (rowWidth - 1) // a mask for offset within the row
rowMask = ^FilterKey(keyMask) // a mask for the row bits, without converting them to a row ID
)
type FilterKey uint64
// FilterResult represents the results of a BitmapFilter considering a
// key, or data. The values are represented as exclusive upper bounds
// on a series of matches followed by a series of rejections. So for
// instance, if called on key 23, the result {YesKey: 23, NoKey: 24}
// indicates that key 23 is a "no" and 24 is unknown and will be the
// next to be Consider()ed. This may seem confusing but it makes the
// math a lot easier to write. It can also report an error, which
// indicates that the entire operation should be stopped with that
// error.
type FilterResult struct {
YesKey FilterKey // The lowest container key this filter is known NOT to match.
NoKey FilterKey // The highest container key after YesKey that this filter is known to not match.
Err error // An error which should terminate processing.
}
// Row() computes the row number of a key.
func (f FilterKey) Row() uint64 {
return uint64(f >> rowExponent)
}
// Add adds an offset to a key.
func (f FilterKey) Add(x uint64) FilterKey {
return f + FilterKey(x)
}
// Sub determines the distance from o to f.
func (f FilterKey) Sub(o FilterKey) uint64 {
return uint64(f - o)
}
// MatchReject just sets Yes and No appropriately.
func (f FilterKey) MatchReject(y, n FilterKey) FilterResult {
return FilterResult{YesKey: y, NoKey: n}
}
func (f FilterKey) MatchOne() FilterResult {
return FilterResult{YesKey: f + 1, NoKey: f + 1}
}
// NeedData() is only really meaningful for ConsiderKey, and indicates
// that a decision can't be made from the key alone.
func (f FilterKey) NeedData() FilterResult {
return FilterResult{}
}
// Fail() reports a fatal error that should terminate processing.
func (f FilterKey) Fail(err error) FilterResult {
return FilterResult{Err: err}
}
// Failf() is just like Errorf, etc
func (f FilterKey) Failf(msg string, args ...interface{}) FilterResult {
return FilterResult{Err: fmt.Errorf(msg, args...)}
}
// MatchRow indicates that the current row matches the filter.
func (f FilterKey) MatchRow() FilterResult {
return FilterResult{YesKey: (f & rowMask) + rowWidth}
}
// MatchOneRejectRow indicates that this item matched but no further
// items in this row can match.
func (f FilterKey) MatchOneRejectRow() FilterResult {
return FilterResult{YesKey: f + 1, NoKey: (f & rowMask) + rowWidth}
}
// Reject rejects this item only.
func (f FilterKey) RejectOne() FilterResult {
return FilterResult{NoKey: f + 1}
}
// Reject rejects N items.
func (f FilterKey) Reject(n uint64) FilterResult {
return FilterResult{NoKey: f.Add(n)}
}
// RejectRow indicates that this entire row is rejected.
func (f FilterKey) RejectRow() FilterResult {
return FilterResult{NoKey: (f & rowMask) + rowWidth}
}
// RejectUntil rejects everything up to the given key.
func (f FilterKey) RejectUntil(until FilterKey) FilterResult {
return FilterResult{NoKey: until}
}
// RejectUntilRow rejects everything until the given row ID.
func (f FilterKey) RejectUntilRow(rowID uint64) FilterResult {
return FilterResult{NoKey: FilterKey(rowID) << rowExponent}
}
// MatchRowUntilRow matches this row, then rejects everything else until
// the given row ID.
func (f FilterKey) MatchRowUntilRow(rowID uint64) FilterResult {
// if rows are 16 wide, "yes" will be 16 minus our current position
// within a row, and "no" will be the distance from the end of our
// current row to the start of rowID, which is also the distance from
// the beginning of our current row to the start of rowID-1.
return FilterResult{
YesKey: (f & rowMask) + rowWidth,
NoKey: FilterKey(rowID) << rowExponent,
}
}
// RejectUntilOffset rejects this container, and any others until the given
// in-row offset.
func (f FilterKey) RejectUntilOffset(offset uint64) FilterResult {
next := (f & rowMask).Add(offset)
if next <= f {
next += rowWidth
}
return FilterResult{NoKey: next}
}
// MatchUntilOffset matches the current container, then skips any other
// containers until the given offset.
func (f FilterKey) MatchOneUntilOffset(offset uint64) FilterResult {
r := f.RejectUntilOffset(offset)
r.YesKey = f + 1
return r
}
// Done indicates that nothing can ever match.
func (f FilterKey) Done() FilterResult {
return FilterResult{
NoKey: ^FilterKey(0),
}
}
// MatchRowAndDone matches this row and nothing after that.
func (f FilterKey) MatchRowAndDone() FilterResult {
return FilterResult{
YesKey: (f & rowMask) + rowWidth,
NoKey: ^FilterKey(0),
}
}
// Match the current container, then skip any others until the same offset
// is reached again.
func (f FilterKey) MatchOneUntilSameOffset() FilterResult {
return f.MatchOneUntilOffset(uint64(f) & keyMask)
}
// A BitmapFilter, given a series of key/data pairs, is considered to "match"
// some of those containers. Matching may be dependent on key values and
// cardinalities alone, or on the contents of the container.
//
// The ConsiderData function must not retain the container, or the data
// from the container; if it needs access to that information later, it needs
// to make a copy.
//
// Many filters are, by virtue of how they operate, able to predict their
// results on future keys. To accommodate this, and allow operations to
// avoid processing keys they don't need to process, the result of a filter
// operation can indicate not just whether a given key matches, but whether
// some upcoming keys will, or won't, match. If ConsiderKey yields a non-zero
// number of matches or non-matches for a given key, ConsiderData will not be
// called for that key.
//
// If multiple filters are combined, they are only called if their input is
// needed to determine a value.
type BitmapFilter interface {
ConsiderKey(key FilterKey, n int32) FilterResult
ConsiderData(key FilterKey, data *Container) FilterResult
}
// ContainerWriteback is the type for functions which can feed updated
// containers back to things from filters.
type ContainerWriteback func(key FilterKey, data *Container) error
// A BitmapRewriter is like a bitmap filter, but can modify the bitmap
// it's being called on during the iteration.
//
// After the last container is returned, ConsiderData will be called with
// an unspecified key and a nil container pointer, so the rewriter can
// write any trailing containers it has. A nil container passed to writeback
// implies a delete operation on the container. Writeback should only be
// called with keys greater than any previously given container key, and
// less than or equal to the current key. So for instance, if
// ConsiderData is called with key 3, and then with key 5, the call with
// key 5 may call writeback with key 4, and then key 5, but may not call
// it with keys 3 or lower, or 6 or higher. When the container provided to
// the call is nil, any monotonically increasing keys greater than the
// previous key are allowed. (If there was no previous key, 0 and higher
// are allowed.)
type BitmapRewriter interface {
ConsiderKey(key FilterKey, n int32) FilterResult
RewriteData(key FilterKey, data *Container, writeback ContainerWriteback) FilterResult
}
// BitmapColumnFilter is a BitmapFilter which checks for containers matching
// a given column within a row; thus, only the one container per row which
// matches the column needs to be evaluated, and it's evaluated as matching
// if it contains the relevant bit.
type BitmapColumnFilter struct {
key, offset uint16
}
var _ BitmapFilter = &BitmapColumnFilter{}
func (f *BitmapColumnFilter) ConsiderKey(key FilterKey, n int32) FilterResult {
if uint16(key&keyMask) != f.key {
return key.RejectUntilOffset(uint64(f.key))
}
return key.NeedData()
}
func (f *BitmapColumnFilter) ConsiderData(key FilterKey, data *Container) FilterResult {
if data.Contains(f.offset) {
return key.MatchOneUntilSameOffset()
}
return key.RejectUntilOffset(uint64(f.key))
}
func NewBitmapColumnFilter(col uint64) BitmapFilter {
return &BitmapColumnFilter{key: uint16((col >> 16) & keyMask), offset: uint16(col & 0xFFFF)}
}
// BitmapRowsFilter is a BitmapFilter which checks for containers that are
// in any of a provided list of rows. The row list should be sorted.
type BitmapRowsFilter struct {
rows []uint64
i int
}
func (f *BitmapRowsFilter) ConsiderKey(key FilterKey, n int32) FilterResult {
if f.i == -1 {
return key.Done()
}
if n == 0 {
return key.RejectOne()
}
row := uint64(key) >> rowExponent
for f.rows[f.i] < row {
f.i++
if f.i >= len(f.rows) {
f.i = -1
return key.Done()
}
}
if f.rows[f.i] > row {
return key.RejectUntilRow(f.rows[f.i])
}
// rows[f.i] must be equal, so we should match this row, until the
// next row, if there is a next row.
if f.i+1 < len(f.rows) {
return key.MatchRowUntilRow(f.rows[f.i+1])
}
return key.MatchRowAndDone()
}
func (f *BitmapRowsFilter) ConsiderData(key FilterKey, data *Container) FilterResult {
return key.Fail(errors.New("bitmap rows filter should never consider data"))
}
func NewBitmapRowsFilter(rows []uint64) BitmapFilter {
if len(rows) == 0 {
return &BitmapRowsFilter{rows: rows, i: -1}
}
return &BitmapRowsFilter{rows: rows, i: 0}
}
// BitmapRowsUnion is a BitmapFilter which produces a union of all the
// rows listed in a []uint64.
type BitmapRowsUnion struct {
c []*Container
rows []uint64
i int
}
func (f *BitmapRowsUnion) ConsiderKey(key FilterKey, n int32) FilterResult {
if f.i == -1 {
return key.Done()
}
if n == 0 {
return key.RejectOne()
}
row := uint64(key) >> rowExponent
for f.rows[f.i] < row {
f.i++
if f.i >= len(f.rows) {
f.i = -1
return key.Done()
}
}
if f.rows[f.i] > row {
return key.RejectUntilRow(f.rows[f.i])
}
// If we ran out of rows, we said we were done. If we're
// waiting for a later row, we said to reject until then.
// Therefore, we're on the current row, and need the data because
// we're going to union it.
return key.NeedData()
}
func (f *BitmapRowsUnion) ConsiderData(key FilterKey, data *Container) FilterResult {
idx := key & keyMask
f.c[idx] = f.c[idx].UnionInPlace(data)
// UnionInPlace with nil will reuse the container. We don't want to reuse
// the container, because ApplyFilter will overwrite it.
if f.c[idx] == data {
f.c[idx] = data.Clone()
}
return key.MatchOne()
}
// Yield the bitmap containing our results, adjusted for a particular shard
// if necessary (because we expect the results to correspond to our shard
// ID).
func (f *BitmapRowsUnion) Results(shard uint64) *Bitmap {
b := NewSliceBitmap()
for i, c := range f.c {
// UnionInPlace might not have fixed count
c.Repair()
b.Containers.Put(uint64(i)+shard*rowWidth, c)
}
return b
}
// Reset the internal container buffer. You must use this before reusing a
// filter.
func (f *BitmapRowsUnion) Reset() {
for i := range f.c {
f.c[i] = nil
}
}
// NewBitmapRowsUnion yields a BitmapRowsUnion which can give you the union
// of all containers matching a given row.
func NewBitmapRowsUnion(rows []uint64) *BitmapRowsUnion {
if len(rows) == 0 {
return &BitmapRowsUnion{rows: rows, i: -1, c: make([]*Container, rowWidth)}
}
return &BitmapRowsUnion{rows: rows, i: 0, c: make([]*Container, rowWidth)}
}
// BitmapRowFilterBase is a generic form of a row-aware wrapper; it
// handles making decisions about keys once you tell it a yesKey and noKey
// that it should be using, and makes callbacks per row.
type BitmapRowFilterBase struct {
FilterResult
callback func(row uint64) error
lastRow uint64
}
var _ BitmapFilter = &BitmapRowFilterBase{}
// DetermineByKey decides whether it can produce a meaningful FilterResult
// for a given key. This encapsulates all the logic for row callbacks and
// figuring out when to wrap a row.
func (b *BitmapRowFilterBase) DetermineByKey(key FilterKey) (FilterResult, bool) {
if b.FilterResult.Err != nil {
return b.FilterResult, true
}
row := key.Row()
if b.YesKey <= key && b.NoKey > key {
return key.RejectUntil(b.NoKey), true
}
if b.lastRow == row {
return key.RejectRow(), true
}
// If we got here: Either b.noKey is less than key, or b.yesKey is
// greater than key. If yesKey is greater, we match this row, and
// possibly update to mark that we've said no through to the end
// of this row.
if b.YesKey > key {
b.lastRow = row
if b.callback != nil {
err := b.callback(row)
if err != nil {
return key.Fail(err), true
}
}
res := key.MatchOneRejectRow()
// This is probably unnecessary, but the idea is, since
// we've decided that we're rejecting everything up to the
// end of this row, we want to be sure that a later call
// doesn't produce a different answer.
if b.NoKey < res.NoKey {
b.NoKey = res.NoKey
}
// if our run of yes answers ends before the rejected row
// ends, and our run of no answers extends beyond this row,
// we can reject until then. note that we can't round that
// up to a full row; if our inner filter were a column
// filter, for instance, that only wanted to see the 7th
// key in each row, we would want to reject up to that 7th
// key, but then look at it.
if b.YesKey <= res.NoKey && b.NoKey > res.NoKey {
res.NoKey = b.NoKey
}
return res, true
}
// Both keys are <= key, err is nil, so this is basically a
// NeedData.
return b.FilterResult, false
}
// SetResult is a convenience function so that things embedding this
// can just call this instead of using a long series of dotted names.
// It returns the new result of DetermineByKey after this change.
func (b *BitmapRowFilterBase) SetResult(key FilterKey, result FilterResult) FilterResult {
b.FilterResult = result
result, _ = b.DetermineByKey(key)
return result
}
// Without a sub-filter, we always-succeed; if we get a key that isn't
// already answered by our YesKey/NoKey/lastRow, we will match this key,
// reject the rest of the row, and update our keys accordingly. We'll
// also hit the callback, and return an error from it if appropriate.
func (b *BitmapRowFilterBase) ConsiderKey(key FilterKey, n int32) FilterResult {
var done bool
b.FilterResult, done = b.DetermineByKey(key)
if done {
return b.FilterResult
}
if n == 0 {
return key.RejectOne()
}
b.FilterResult = key.MatchOneRejectRow()
row := key.Row()
b.lastRow = row
if b.callback != nil {
b.Err = b.callback(row)
}
return b.FilterResult
}
// This should probably never be reached?
func (b *BitmapRowFilterBase) ConsiderData(key FilterKey, data *Container) FilterResult {
b.Err = errors.New("base iterator should never consider data")
return b.FilterResult
}
func NewBitmapRowFilterBase(callback func(row uint64) error) *BitmapRowFilterBase {
return &BitmapRowFilterBase{lastRow: ^uint64(0), callback: callback}
}
type BitmapRowLimitFilter struct {
BitmapRowFilterBase
limit uint64
}
var _ BitmapFilter = &BitmapRowLimitFilter{}
// Without a sub-filter, we always-succeed; if we get a key that isn't
// already answered by our YesKey/NoKey/lastRow, we will match the whole
// row.
func (b *BitmapRowLimitFilter) ConsiderKey(key FilterKey, n int32) FilterResult {
var done bool
b.FilterResult, done = b.DetermineByKey(key)
if done {
return b.FilterResult
}
if n == 0 {
return key.RejectOne()
}
if b.limit > 0 {
b.FilterResult = key.MatchRow()
b.limit--
} else {
b.FilterResult = key.Done()
}
return b.FilterResult
}
// This should probably never be reached?
func (b *BitmapRowLimitFilter) ConsiderData(key FilterKey, data *Container) FilterResult {
b.Err = errors.New("limit iterator should never consider data")
return b.FilterResult
}
func NewBitmapRowLimitFilter(limit uint64) *BitmapRowLimitFilter {
return &BitmapRowLimitFilter{BitmapRowFilterBase: *NewBitmapRowFilterBase(nil), limit: limit}
}
// BitmapRowFilterSingleFilter is a row iterator with a single
// filter, which is simpler than one with multiple filters where
// it coincidentally turns out that N==1.
type BitmapRowFilterSingleFilter struct {
BitmapRowFilterBase
filter BitmapFilter
}
func (b *BitmapRowFilterSingleFilter) ConsiderKey(key FilterKey, n int32) FilterResult {
res, done := b.DetermineByKey(key)
if done {
return res
}
return b.SetResult(key, b.filter.ConsiderKey(key, n))
}
func (b *BitmapRowFilterSingleFilter) ConsiderData(key FilterKey, data *Container) FilterResult {
// We already handled any consideration of the key above, in principle.
b.FilterResult = b.filter.ConsiderData(key, data)
if b.FilterResult.Err != nil {
return b.FilterResult
}
res, done := b.DetermineByKey(key)
if done {
return res
}
// We could just return the res, which would say nothing, but I
// think it should be a visible error if that happens.
b.FilterResult.Err = errors.New("inner filter didn't make a decision")
return b.FilterResult
}
func NewBitmapRowFilterSingleFilter(callback func(row uint64) error, filter BitmapFilter) *BitmapRowFilterSingleFilter {
return &BitmapRowFilterSingleFilter{
BitmapRowFilterBase: BitmapRowFilterBase{lastRow: ^uint64(0), callback: callback},
filter: filter,
}
}
// BitmapRowFilterMultiFilter is a BitmapFilter which wraps other bitmap filters,
// calling a callback function once per row whenever it finds a container
// for which all the filters returned true.
type BitmapRowFilterMultiFilter struct {
BitmapRowFilterBase
filters []BitmapFilter
yesKeys, noKeys []FilterKey
toDo []int
}
func (b *BitmapRowFilterMultiFilter) ConsiderKey(key FilterKey, n int32) FilterResult {
res, done := b.DetermineByKey(key)
if done {
return res
}
// highestNo: The highest No value that we have that isn't preceeded
// by a relevant Yes.
highestNo := key
lowestYes := ^FilterKey(0)
// The length of the "no" run after the lowest "yes"
lowestYesNo := FilterKey(0)
b.toDo = b.toDo[:0]
// We scan for any no values that don't have an earlier yes that's
// still greater than this key. If there are any, we can skip to
// the highest such value immediately. We also build a todo list
// of items for which we have neither a yes nor a no answer greater
// than this key.
for i, yk := range b.yesKeys {
if yk > key {
if yk < lowestYes {
lowestYes = yk
lowestYesNo = b.noKeys[i]
}
continue
}
nk := b.noKeys[i]
if nk > highestNo {
highestNo = nk
continue
}
b.toDo = append(b.toDo, i)
}
// We have an unambiguous no, so we can set our internal state to
// be aware that we have a No until then. We can unconditionally
// return the result; it can't be not-done, because we just set
// it to a known done state.
if highestNo > key {
return b.SetResult(key, key.RejectUntil(highestNo))
}
// Everything either has a yes value which is at least as high
// as lowestYes, or is in f.toDo now. Now we call ConsiderKey
// for everything in f.toDo, and accumulate a new list of the
// values still don't know, using the same backing store.
newToDo := b.toDo[:0]
for _, filter := range b.toDo {
result := b.filters[filter].ConsiderKey(key, n)
if result.Err != nil {
return key.Fail(result.Err)
}
yk, nk := result.YesKey, result.NoKey
b.yesKeys[filter], b.noKeys[filter] = yk, nk
if yk > key {
if lowestYes == 0 || yk < lowestYes {
lowestYes = yk
lowestYesNo = nk
}
continue
}
if nk > highestNo {
highestNo = nk
continue
}
newToDo = append(newToDo, filter)
}
// Same logic as before; if we have a highestNo, we don't need more
// information.
if highestNo > key {
return b.SetResult(key, key.RejectUntil(highestNo))
}
b.toDo = newToDo
if len(b.toDo) > 0 {
return key.NeedData()
}
// this shouldn't be possible
if lowestYes <= key {
return key.Failf("got lowest yes %d for key %d, this shouldn't happen", lowestYes, key)
}
// Flag that we have a definite Yes as far as the lowest yes, and a
// definite No after that to the corresponding No.
return b.SetResult(key, key.MatchReject(lowestYes, lowestYesNo))
}
// ConsiderData only gets called in cases where f.toDo had a list of filters
// for which we needed to get data to make a decision. That means that
// everything but the indexes in f.toDo must be a "yes" right now.
func (b *BitmapRowFilterMultiFilter) ConsiderData(key FilterKey, data *Container) FilterResult {
res, done := b.DetermineByKey(key)
if done {
return res
}
highestNo := key
for _, filter := range b.toDo {
result := b.filters[filter].ConsiderData(key, data)
if result.Err != nil {
return key.Fail(result.Err)
}
yk, nk := result.YesKey, result.NoKey
b.yesKeys[filter], b.noKeys[filter] = yk, nk
if yk <= key && nk > highestNo {
highestNo = nk
}
}
if highestNo > key {
return b.SetResult(key, key.RejectUntil(highestNo))
}
// if we got here, either something was buggy, or everything has a yes
// > key.
lowestYes := ^FilterKey(0)
lowestYesNo := key
for i, yk := range b.yesKeys {
if yk < lowestYes {
lowestYes = yk
lowestYesNo = b.noKeys[i]
}
}
// this shouldn't be possible
if lowestYes <= key {
return key.Failf("got lowest yes %d on data for key %d, this shouldn't happen", lowestYes, key)
}
return b.SetResult(key, key.MatchReject(lowestYes, lowestYesNo))
}
// BitmapBitmap filter builds a list of positions in the bitmap which
// match those in a provided bitmap. It is shard-agnostic; no matter what
// offsets the input bitmap's containers have, it matches them against
// corresponding keys.
type BitmapBitmapFilter struct {
containers []*Container
nextOffsets []uint64
callback func(uint64) error
}
func (b *BitmapBitmapFilter) SetCallback(cb func(uint64) error) {
b.callback = cb
}
func (b *BitmapBitmapFilter) ConsiderKey(key FilterKey, n int32) FilterResult {
pos := key & keyMask
if b.containers[pos] == nil || n == 0 {
return key.RejectUntilOffset(b.nextOffsets[pos])
}
return key.NeedData()
}
func (b *BitmapBitmapFilter) ConsiderData(key FilterKey, data *Container) FilterResult {
pos := key & keyMask
base := uint64(key << 16)
filter := b.containers[pos]
if filter == nil {
return key.RejectUntilOffset(b.nextOffsets[pos])
}
var lastErr error
matched := false
intersectionCallback(data, filter, func(v uint16) {
matched = true
err := b.callback(base + uint64(v))
if err != nil {
lastErr = err
}
})
if lastErr != nil {
return key.Fail(lastErr)
}
if !matched {
return key.RejectUntilOffset(b.nextOffsets[pos])
}
return key.MatchOneUntilOffset(b.nextOffsets[pos])
}
// NewBitmapBitmapFilter creates a filter which can report all the positions
// within a bitmap which are set, and which have positions corresponding to
// the specified columns. It calls the provided callback function on
// each value it finds, terminating early if that returns an error.
//
// The input filter is assumed to represent one "row" of a shard's data,
// which is to say, a range of up to rowWidth consecutive containers starting
// at some multiple of rowWidth. We coerce that to the 0..rowWidth range
// because offset-within-row is what we care about.
func NewBitmapBitmapFilter(filter *Bitmap, callback func(uint64) error) *BitmapBitmapFilter {
b := &BitmapBitmapFilter{
callback: callback,
containers: make([]*Container, rowWidth),
nextOffsets: make([]uint64, rowWidth),
}
iter, _ := filter.Containers.Iterator(0)
last := uint64(0)
count := 0
for iter.Next() {
k, v := iter.Value()
// Coerce container key into the 0-rowWidth range we'll be
// using to compare against containers within each row.
k = k & keyMask
b.containers[k] = v
last = k
count++
}
// if there's only one container, we need to populate everything with
// its position.
if count == 1 {
for i := range b.containers {
b.nextOffsets[i] = last
}
} else {
// Point each container at the offset of the next valid container.
// With sparse bitmaps this will potentially make skipping faster.
for i := range b.containers {
if b.containers[i] != nil {
for int(last) != i {
b.nextOffsets[last] = uint64(i)
last = (last + 1) % rowWidth
}
}
}
}
return b
}
// BitmapRowFilterMultiFilter will call a
func NewBitmapRowFilterMultiFilter(callback func(row uint64) error, filters ...BitmapFilter) BitmapFilter {
return &BitmapRowFilterMultiFilter{
filters: filters,
yesKeys: make([]FilterKey, len(filters)),
noKeys: make([]FilterKey, len(filters)),
BitmapRowFilterBase: BitmapRowFilterBase{
callback: callback,
lastRow: ^uint64(0),
},
}
}
// BitmapRowLister returns a pointer to a slice which it will populate when invoked
// as a bitmap filter.
func NewBitmapRowFilter(callback func(uint64) error, filters ...BitmapFilter) BitmapFilter {
if len(filters) == 0 {
return NewBitmapRowFilterBase(callback)
}
if len(filters) == 1 {
return NewBitmapRowFilterSingleFilter(callback, filters[0])
}
return NewBitmapRowFilterMultiFilter(callback, filters...)
}
// BitmapRangeFilter limits filter operations to a specified range, and
// performs key or data callbacks.
//
// On seeing a key in its range:
// If the key callback is present, and returns true, match the key.
// Otherwise, if a data callback is present, request the data, and in the
// data handler, call the data callback, then match the single key.
// If neither is present, match the entire range at once.
type BitmapRangeFilter struct {
min, max FilterKey
kcb func(FilterKey, int32) (bool, error)
dcb func(FilterKey, *Container) error
}
var _ BitmapFilter = &BitmapRangeFilter{}
func (b *BitmapRangeFilter) ConsiderKey(key FilterKey, n int32) FilterResult {
if key >= b.max {
return key.Done()
}
if key >= b.min {
if b.kcb != nil {
match, err := b.kcb(key, n)
if err != nil {
return key.Fail(err)
}
if match {
return key.MatchOne()
}
}
if b.dcb != nil {
return key.NeedData()
}
return key.MatchReject(b.max, ^FilterKey(0))
}
return key.RejectUntil(b.min)
}
func (b *BitmapRangeFilter) ConsiderData(key FilterKey, data *Container) FilterResult {
err := b.dcb(key, data)
if err != nil {
return key.Fail(err)
}
return key.MatchOne()
}
func NewBitmapRangeFilter(min, max FilterKey, keyCallback func(FilterKey, int32) (bool, error), dataCallback func(FilterKey, *Container) error) *BitmapRangeFilter {
return &BitmapRangeFilter{min: min, max: max, kcb: keyCallback, dcb: dataCallback}
}
// BitmapMutexDupFilter is a filter which identifies cases where the same
// position has a bit set in more than one row.
//
// We keep a slice of the first value seen for every row, with ^0 as the
// default; when that's already set, things get appended to the entries in
// the map. At the end, for each entry in the map, we also add its first
// value to it. Thus, the map holds all the entries, but we're only using
// the map in the (hopefully rarer) cases where there's duplicate values.
//
// The slice is local-coordinates (first column 0), but the map is global
// coordinates (first column is whatever base was).
type BitmapMutexDupFilter struct {
base uint64 // the offset of 0 for this, used to accommodate shard offsets
extra map[uint64][]uint64 // extra values observed
first []uint64 // first values observed
details bool
limit int
done bool // if we have a limit, and we've hit it...
highKey FilterKey // ... we can stop after this many containers.
}
var _ BitmapFilter = &BitmapMutexDupFilter{}
func NewBitmapMutexDupFilter(base uint64, details bool, limit int) *BitmapMutexDupFilter {
filter := &BitmapMutexDupFilter{
base: base,
extra: map[uint64][]uint64{},
first: make([]uint64, 1<<shardwidth.Exponent),
details: details,
limit: limit,
}
if filter.limit == 0 {
// A limit of 0 is not a limit; set limit higher than possible number of
// values we could have.
filter.limit = 2 << shardwidth.Exponent
}
for i := range filter.first {
filter.first[i] = ^uint64(0)
}
return filter
}
func (b *BitmapMutexDupFilter) ConsiderKey(key FilterKey, n int32) FilterResult {
if n > 0 {
return key.NeedData()
}
return key.RejectOne()
}
func (b *BitmapMutexDupFilter) ConsiderData(key FilterKey, data *Container) FilterResult {
value, basePos := uint64(key)>>rowExponent, uint64(key&keyMask)<<16
ContainerCallback(data, func(u uint16) {
pos := basePos + uint64(u)
if b.first[pos] != ^uint64(0) {
if b.details {
b.extra[pos+b.base] = append(b.extra[pos+b.base], value)
} else {
// no details, just annotate that it exists
b.extra[pos+b.base] = []uint64{}
}
} else {
b.first[pos] = value
}
})
if len(b.extra) >= b.limit {
if !b.done {
// we note which container we found the last value we needed in.
// We may still go over the limit, but we won't look at any *more*
// containers in this row.
//
// We can't just abort early because the records we already found
// could have more values.
b.done = true
b.highKey = key & keyMask
return key.RejectRow()
}
if (key & keyMask) >= b.highKey {
return key.RejectRow()
}
}
return key.MatchOne()
}
// Report returns the set of duplicate values identified.
func (b *BitmapMutexDupFilter) Report() map[uint64][]uint64 {
// copy values into extra, and remove them from first, so calling
// Report() again won't cause double-appends. We only have to do
// this if we've been asked for details; otherwise the list of
// known positions is sufficient.
if b.details {
for k, v := range b.extra {
kpos := k % (1 << shardwidth.Exponent)
if b.first[kpos] != ^uint64(0) {
v = append(v, 0)
// prepend so the lowest value goes at the beginning
copy(v[1:], v[:])
v[0] = b.first[kpos]
b.first[kpos] = ^uint64(0)
b.extra[k] = v
}
}
}
return b.extra
}
// BitmapBitmapTrimmer is like BitmapBitmapFilter, but instead of calling
// a callback per bit found in the intersection, it calls a callback with the
// original raw container and the corresponding filter container, and also
// provides the writeback func it got from the bitmap. So for instance, to
// implement a "subtract these bits" function, you would difference-in-place
// the raw container with the filter container, then pass that to the writeback
// function.
//
// It's called a Trimmer because it won't add containers; it won't *add*
// containers. It calls the callback function for every container, whether or
// not it matches the filter; this allows an intersect-like filter to work
// too.
//
// Note, however, that the caller's ContainerWriteback function *may* create
// containers, even though the Trimmer won't have called RewriteData with those
// keys.
type BitmapBitmapTrimmer struct {
containers []*Container
nextOffsets []uint64
callback func(key FilterKey, raw, filter *Container, writeback ContainerWriteback) error
}
var _ BitmapRewriter = &BitmapBitmapTrimmer{}
func (b *BitmapBitmapTrimmer) SetCallback(cb func(FilterKey, *Container, *Container, ContainerWriteback) error) {
b.callback = cb
}
func (b *BitmapBitmapTrimmer) ConsiderKey(key FilterKey, n int32) FilterResult {
pos := key & keyMask
// If a trimmer wants to do something for a key, it *must* have something in
// the filter slot for that key, otherwise we won't call it with the
// corresponding existing data. For the mutex case, this is covered -- every
// bit we have to write implies the corresponding filter bit being set.
//
// Note that, unlike BitmapBitmapFilter, we don't make assumptions about
// what you are *doing* with the filter.
if b.containers[pos] == nil || n == 0 {
return key.RejectUntilOffset(b.nextOffsets[pos])
}
return key.NeedData()
}
func (b *BitmapBitmapTrimmer) RewriteData(key FilterKey, data *Container, writeback ContainerWriteback) FilterResult {
pos := key & keyMask
filter := b.containers[pos]
err := b.callback(key, data, filter, writeback)
if err != nil {
return key.Fail(err)
}
return key.MatchOneUntilOffset(b.nextOffsets[pos])
}
// NewBitmapBitmapTrimmer creates a filter which calls a callback on every
// container in a bitmap, with corresponding elements from an initial filter
// bitmap. It does not call its callback for cases where there's no container
// in the original bitmap.
//
// The input filter is assumed to represent one "row" of a shard's data,
// which is to say, a range of up to rowWidth consecutive containers starting
// at some multiple of rowWidth. We coerce that to the 0..rowWidth range
// because offset-within-row is what we care about.
func NewBitmapBitmapTrimmer(filter *Bitmap, callback func(FilterKey, *Container, *Container, ContainerWriteback) error) *BitmapBitmapTrimmer {
b := &BitmapBitmapTrimmer{
callback: callback,
containers: make([]*Container, rowWidth),
nextOffsets: make([]uint64, rowWidth),
}
iter, _ := filter.Containers.Iterator(0)
last := uint64(0)
count := 0
for iter.Next() {
k, v := iter.Value()
// Coerce container key into the 0-rowWidth range we'll be
// using to compare against containers within each row.
k = k & keyMask
b.containers[k] = v
last = k
count++
}
// if there's only one container, we need to populate everything with
// its position.
if count == 1 {
for i := range b.containers {
b.nextOffsets[i] = last
}
} else {
// Point each container at the offset of the next valid container.
// With sparse bitmaps this will potentially make skipping faster.
for i := range b.containers {
if b.containers[i] != nil {
for int(last) != i {
b.nextOffsets[last] = uint64(i)
last = (last + 1) % rowWidth
}
}
}
}
return b
}
// ApplyFilterToIterator is a simplistic implementation that applies a bitmap
// filter to a ContainerIterator, returning an error if it encounters an error.
//
// This mostly exists for testing purposes; a Tx implementation where generating
// containers is expensive should almost certainly implement a better way to
// use filters which only generates data if it needs to.
func ApplyFilterToIterator(filter BitmapFilter, iter ContainerIterator) error {
defer iter.Close()
var until = uint64(0)
for (until < ^uint64(0)) && iter.Next() {
key, data := iter.Value()
if key < until {
continue
}
result := filter.ConsiderKey(FilterKey(key), data.N())
if result.Err != nil {
return result.Err
}
until = uint64(result.NoKey)
if key < until {
continue
}
result = filter.ConsiderData(FilterKey(key), data)
if result.Err != nil {
return result.Err
}
until = uint64(result.NoKey)
}
return nil
}
// BitmapBSICountFilter gives counts of values in each value-holding row
// of a BSI field, constrained by a filter. The first row of the data is
// taken to be an existence bit, which is intersected into the filter to
// constrain it, and the second is used as a sign bit. The rows after that
// are treated as value rows, and their counts of bits, overlapping with
// positive and negative bits in the sign rows, are returned to a callback
// function.
//
// The total counts of positions evaluated are returned with a row count
// of ^uint64(0) prior to row counts.
type BitmapBSICountFilter struct {
containers []*Container
positive []*Container
negative []*Container
nextOffsets []uint64
count int32
psum, nsum uint64
}
func (b *BitmapBSICountFilter) Total() (count int32, total int64) {
return b.count, int64(b.psum) - int64(b.nsum)
}
func (b *BitmapBSICountFilter) ConsiderKey(key FilterKey, n int32) FilterResult {
pos := key & keyMask
if b.containers[pos] == nil || n == 0 {
return key.RejectUntilOffset(b.nextOffsets[pos])
}
return key.NeedData()
}
func (b *BitmapBSICountFilter) ConsiderData(key FilterKey, data *Container) FilterResult {
pos := key & keyMask
filter := b.containers[pos]
if filter == nil {
return key.RejectUntilOffset(b.nextOffsets[pos])
}
row := uint64(key >> rowExponent) // row count within the fragment
// How do we translate the filter and existence bit into actionable things?
// Assume the sign row is empty. We want positive values for anything in
// the intersection of the filter and the positive bits. If the sign row
// isn't empty, we want positive values for that intersection, less the
// sign row, and negative for the intersection of the filter/positive and
// the sign bits. So we can just stash the intermediate filter+existence
// as positive, then split it up if we have sign bits, which we often don't.
setup := false
switch row {
case 0: // existence bit
b.positive[pos] = intersect(b.containers[pos], data)
if b.positive[pos] == data {
b.positive[pos] = b.positive[pos].Clone()
}
b.count += int32(b.positive[pos].N())
setup = true
case 1: // sign bit
// split into negative/positive components. doesn't affect total
// count.
b.negative[pos] = intersect(b.positive[pos], data)
if b.negative[pos] == data {
b.negative[pos] = b.negative[pos].Clone()
}
b.positive[pos] = difference(b.positive[pos], data)
setup = true
}
// if we were doing setup (first two rows), we're done
if setup {
return key.MatchOneUntilOffset(b.nextOffsets[pos])
}
// helpful reminder: a nil container is a valid empty container, and
// intersectionCount knows this.
pcount := intersectionCount(b.positive[pos], data)
ncount := intersectionCount(b.negative[pos], data)
b.psum += (uint64(pcount) << (row - 2))
b.nsum += (uint64(ncount) << (row - 2))
return key.MatchOneUntilOffset(b.nextOffsets[pos])
}
// NewBitmapBSICountFilter creates a BitmapBSICountFilter, used for tasks
// like computing the sum of a BSI field matching a given filter.
//
// The input filter is assumed to represent one "row" of a shard's data,
// which is to say, a range of up to rowWidth consecutive containers starting
// at some multiple of rowWidth. We coerce that to the 0..rowWidth range
// because offset-within-row is what we care about.
func NewBitmapBSICountFilter(filter *Bitmap) *BitmapBSICountFilter {
containers := make([]*Container, rowWidth*3)
b := &BitmapBSICountFilter{
containers: containers[:rowWidth],
positive: containers[rowWidth : rowWidth*2],
negative: containers[rowWidth*2 : rowWidth*3],
nextOffsets: make([]uint64, rowWidth),
}
if filter == nil {
for i := range b.containers {
b.containers[i] = NewContainerRun([]Interval16{{Start: 0, Last: 65535}})
b.nextOffsets[i] = uint64(i+1) % rowWidth
}
return b
}
count := 0
iter, _ := filter.Containers.Iterator(0)
last := uint64(0)
for iter.Next() {
k, v := iter.Value()
// Coerce container key into the 0-rowWidth range we'll be
// using to compare against containers within each row.
k = k & keyMask
b.containers[k] = v
last = k
count++
}
// if there's only one container, we need to populate everything with
// its position.
if count == 1 {
for i := range b.containers {
b.nextOffsets[i] = last
}
} else {
// Point each container at the offset of the next valid container.
// With sparse bitmaps this will potentially make skipping faster.
for i := range b.containers {
if b.containers[i] != nil {
for int(last) != i {
b.nextOffsets[last] = uint64(i)
last = (last + 1) % rowWidth
}
}
}
}
return b
}
// getNextFromIterator is a convenience function which calls Next and then Value
// on a ContainerIterator and changes the key to a FilterKey, and
// returns KEY_DONE if the iterator is done.
func getNextFromIterator(contIter ContainerIterator) (FilterKey, *Container) {
if !contIter.Next() {
return KEY_DONE, nil
}
key, val := contIter.Value()
return FilterKey(key), val
}
// NewRepeatedRowIteratorFromBytes interprets "data" as a roaring
// bitmap and returns a ContainerIterator which will repeatedly return
// the first "row" of data as determined by shard width, but with
// increasing keys. It is essentially a conveniece wrapper around
// NewContainerIterator and NewRepeatedRowContainerIterator. It treats
// empty "data" as valid and returns a no-op iterator.
func NewRepeatedRowIteratorFromBytes(data []byte) (ContainerIterator, error) {
iter, err := NewContainerIterator(data)
if err != nil {
return nil, errors.Wrap(err, "getting container iterator")
}
return NewRepeatedRowContainerIterator(iter), nil
}
// NewRepeatedRowContainerIterator returns a ContainerIterator which
// reads the first "row" of containers out of iter (as determined by
// shard width, so up to and including key 15 by default). It then
// returns a ContainerIterator which will repeatedly emit the
// containers in that row, but with increasing keys such that each
// time a particular container is emitted it has its key from the last
// time plus number of containers in a row (16 by default).
func NewRepeatedRowContainerIterator(iter ContainerIterator) *repeatedRowIterator {
conts := getFirstRowAsContainers(iter)
return &repeatedRowIterator{
containers: conts,
cur: -1,
}
}
type containerWithKey struct {
*Container
key FilterKey
}
type repeatedRowIterator struct {
containers []containerWithKey
row uint64
cur int
}
func (r *repeatedRowIterator) Next() bool {
r.cur++
if r.cur >= len(r.containers) {
r.cur = 0
r.row++
}
return len(r.containers) > 0
}
func (r *repeatedRowIterator) Value() (uint64, *Container) {
if len(r.containers) == 0 {
return 0, nil
}
return r.row*rowWidth + uint64(r.containers[r.cur].key%rowWidth), r.containers[r.cur].Container
}
func (r *repeatedRowIterator) Close() {}
func getFirstRowAsContainers(citer ContainerIterator) []containerWithKey {
citerContainers := make([]containerWithKey, 0)
for citer.Next() {
key, cont := citer.Value()
if key >= rowWidth {
continue
}
citerContainers = append(citerContainers, containerWithKey{Container: cont, key: FilterKey(key)})
}
return citerContainers
}
// NewClearAndSetRewriter instantiates a ClearAndSetRewriter
func NewClearAndSetRewriter(clear, set ContainerIterator) (*ClearAndSetRewriter, error) {
curSetKey, curSet := getNextFromIterator(set)
curClearKey, curClear := getNextFromIterator(clear)
return &ClearAndSetRewriter{
curSetKey: curSetKey,
curSet: curSet,
curClearKey: curClearKey,
curClear: curClear,
clearIter: clear,
setIter: set,
}, nil
}
const KEY_DONE FilterKey = math.MaxUint64
// ClearAndSetRewriter is a BitmapRewriter which can operate on two
// ContainerIterators, clearing bits from one and setting bits from
// the other. It tries to do this pretty efficiently such that it
// doesn't look at the clear iterator unless there is actually a
// container that might need bits cleared, and it doesn't write a
// container unless bits have actually changed.
type ClearAndSetRewriter struct {
curSetKey FilterKey
curSet *Container
curClearKey FilterKey
curClear *Container
clearIter ContainerIterator
setIter ContainerIterator
}
func (csr *ClearAndSetRewriter) nextClear() (FilterKey, *Container) {
csr.curClearKey, csr.curClear = getNextFromIterator(csr.clearIter)
return csr.curClearKey, csr.curClear
}
func (csr *ClearAndSetRewriter) nextSet() (FilterKey, *Container) {
csr.curSetKey, csr.curSet = getNextFromIterator(csr.setIter)
return csr.curSetKey, csr.curSet
}
func (csr *ClearAndSetRewriter) lowestKey() FilterKey {
if csr.curSetKey < csr.curClearKey {
return csr.curSetKey
}
return csr.curClearKey
}
func (csr *ClearAndSetRewriter) ConsiderKey(key FilterKey, n int32) FilterResult {
if key < csr.lowestKey() {
return key.RejectUntil(csr.lowestKey())
}
return key.NeedData()
}
// writeCurrent writes the current clear and set if they match the
// key. It takes some care to try to determine if any bits actually
// changed to avoid unnecessary writes.
func (csr *ClearAndSetRewriter) writeCurrent(key FilterKey, data *Container, writeback ContainerWriteback) error {
if key == KEY_DONE {
return nil
}
changed := false
startN := data.N()
if csr.curClearKey == key && csr.curSetKey == key {
actualClear := csr.curClear.Difference(csr.curSet) // don't need to clear bits that we're going to set
data = data.DifferenceInPlace(actualClear)
clearedN := data.N()
changed = clearedN != startN
data = data.UnionInPlace(csr.curSet)
data.Repair()
setN := data.N()
changed = changed || clearedN != setN
} else if csr.curClearKey == key {
data = data.DifferenceInPlace(csr.curClear)
clearedN := data.N()
changed = clearedN != startN
} else if csr.curSetKey == key {
data = data.UnionInPlace(csr.curSet)
data.Repair()
setN := data.N()
changed = startN != setN
}
if changed {
if err := writeback(key, data); err != nil {
return err
}
}
return nil
}
func (csr *ClearAndSetRewriter) RewriteData(key FilterKey, data *Container, writeback ContainerWriteback) FilterResult {
if data == nil {
key = KEY_DONE
}
// when we're called with a container, we need to process all the
// sets up to that container that we haven't done yet. Then we
// need to do any clears on that container, then any sets on that
// container. Then we can reject until the lowest next container.
// When we enter this function, curSet and curClear are the next
// things we need to process. When we leave this function they
// must be set to the next things we need to process.
for ; csr.curSetKey < key; csr.nextSet() {
if err := writeback(csr.curSetKey, csr.curSet); err != nil {
return key.Fail(errors.Wrapf(err, "writing set container at %d", csr.curSetKey))
}
}
// fast forward clears to this key
for ; csr.curClearKey < key && key != KEY_DONE; csr.nextClear() {
}
err := csr.writeCurrent(key, data, writeback)
if err != nil {
return key.Fail(errors.Wrapf(err, "writing current key %d", key))
}
if csr.curSetKey == key {
csr.nextSet()
}
if csr.curClearKey == key {
csr.nextClear()
}
return key.RejectUntil(csr.lowestKey())
}