// 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< 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()) }