featurebase/roaring/filter.go
Travis Turner aa17b8d725
Enable linter: stylecheck (#2317)
* Enable linter: stylecheck

This enabled the stylecheck linter, but excludes some staticchecks for
now. The following are ignored because they will take a bit of time to
address, but the intention is to address them and remove them from the
exclusion list.

ST1000: at least one file in a package should have a package comment
ST1003: golang naming standards
ST1008: error should be returned as the last argument
ST1016: methods on the same type should have the same receiver name
ST1020: comment on exported function

* Address ST1015

For some reason this failed in CI but not locally. I can't figure out
why that check isn't happening locally. This just moves the switch
statements around so that the `default` is the first (or last) item.

* Adjust error string in test to match case-adjusted error

* Remove TestCloseTimeout
2023-03-14 08:45:18 -05:00

1424 lines
46 KiB
Go

// Copyright 2022 Molecula Corp. (DBA FeatureBase).
// SPDX-License-Identifier: Apache-2.0
package roaring
import (
"fmt"
"math"
"github.com/pkg/errors"
"github.com/featurebasedb/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)
}
// BitmapFilter is, given a series of key/data pairs, 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))
}
// BitmapBitmapFilter 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())
}