mirror of
https://github.com/featurebasedb/featurebase.git
synced 2026-08-28 10:54:59 +00:00
So in some cases, when we do a query, the results of one part of the query are innately shared-across-nodes; for instance, a hypothetical Distinct query. More generally, we allow cross-index queries; calls can have "index=foo" in them. This patch lets us handle that without duplicating that query all over. Before we actually start doing the separate calls, we run the query once from the coordinating node, then patch the results in, and send relevant subsets over to each client, etcetera. Also provides slightly friendlier (and I hope faster) support for converting bitmaps to/from sets of rows. We also add an extension interface, and some fancy stuff to let us define new calls, which use this. They're sort of tied together because the first extension I wanted to implement needed precomputed calls. The extension API lets us create extensions using `pkg/plugin` (with all its associated limitations, unfortunately), then query them at load time for functionality. This also implies some revamping of the argument validation for PQL, like verifying that functions exist and knowing things about their argument types. So basically this is an overly intrusive patch, and would be better as separate patches, but they're hard to detangle. add trivial execution-time profiling What if you could ?profile=true on a query and get some numbers back? That'd be really cool. We already have tracing/spans, but right now, those only generate any data if you have something set up for them to trace to. Add a fancy wrapper that lets us generate our own tracing data, and dump it into the request response, if ?profile=true. add a sample extension, add missing features to extension interface Implement a naive probabilistic filter extension as an example of what an extension looks like. In the process, discover multiple omissions in the bitmap API. Well, I did *say* it was experimental.
729 lines
18 KiB
Go
729 lines
18 KiB
Go
// Copyright 2017 Pilosa Corp.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package pilosa
|
|
|
|
import (
|
|
"encoding/json"
|
|
"sort"
|
|
|
|
"github.com/pilosa/pilosa/v2/ext"
|
|
"github.com/pilosa/pilosa/v2/roaring"
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
// Row is a set of integers (the associated columns), and attributes which are
|
|
// arbitrary key/value pairs storing metadata about what the row represents.
|
|
type Row struct {
|
|
segments []rowSegment
|
|
|
|
// String keys translated to/from segment columns.
|
|
Keys []string
|
|
|
|
// Attributes associated with the row.
|
|
Attrs map[string]interface{}
|
|
}
|
|
|
|
// NewRow returns a new instance of Row.
|
|
func NewRow(columns ...uint64) *Row {
|
|
r := &Row{}
|
|
for _, i := range columns {
|
|
r.SetBit(i)
|
|
}
|
|
return r
|
|
}
|
|
|
|
// NewRowFromBitmap divides a bitmap into rows, which it now calls shards. This
|
|
// transposes; data that was in any shard for Row 0 is now considered shard 0,
|
|
// etcetera.
|
|
func NewRowFromBitmap(b *roaring.Bitmap) *Row {
|
|
r := &Row{}
|
|
if b == nil {
|
|
return r
|
|
}
|
|
rowNum := uint64(0)
|
|
for col, ok := b.MinAt(rowNum * ShardWidth); ok; col, ok = b.MinAt(rowNum * ShardWidth) {
|
|
rowNum = col / ShardWidth
|
|
seg := rowSegment{
|
|
shard: rowNum,
|
|
data: b.OffsetRange(rowNum*ShardWidth, rowNum*ShardWidth, (rowNum+1)*ShardWidth),
|
|
writable: true,
|
|
}
|
|
seg.n = seg.data.Count()
|
|
r.segments = append(r.segments, seg)
|
|
rowNum++
|
|
}
|
|
return r
|
|
}
|
|
|
|
// NewRowFromRoaring parses a roaring data file as a row, dividing it into
|
|
// bitmaps and rowSegments based on shard width.
|
|
func NewRowFromRoaring(data []byte) *Row {
|
|
bitmaps, shards := roaring.RoaringToBitmaps(data, ShardWidth)
|
|
r := &Row{segments: make([]rowSegment, len(bitmaps))}
|
|
for i := range bitmaps {
|
|
segment := rowSegment{
|
|
shard: shards[i],
|
|
data: bitmaps[i],
|
|
writable: false,
|
|
n: bitmaps[i].Count(),
|
|
}
|
|
r.segments[i] = segment
|
|
}
|
|
return r
|
|
}
|
|
|
|
// Roaring returns the row treated as a unified roaring bitmap.
|
|
func (r *Row) Roaring() []byte {
|
|
bitmaps := make([]*roaring.Bitmap, len(r.segments))
|
|
for i := range r.segments {
|
|
bitmaps[i] = r.segments[i].data
|
|
}
|
|
return roaring.BitmapsToRoaring(bitmaps)
|
|
}
|
|
|
|
// IsEmpty returns true if the row doesn't contain any set bits.
|
|
func (r *Row) IsEmpty() bool {
|
|
if len(r.segments) == 0 {
|
|
return true
|
|
}
|
|
for i := range r.segments {
|
|
if r.segments[i].n > 0 {
|
|
return false
|
|
}
|
|
|
|
}
|
|
return true
|
|
}
|
|
|
|
func (r *Row) Freeze() {
|
|
for _, s := range r.segments {
|
|
s.Freeze()
|
|
}
|
|
}
|
|
|
|
// Merge merges data from other into r.
|
|
func (r *Row) Merge(other *Row) {
|
|
var segments []rowSegment
|
|
|
|
itr := newMergeSegmentIterator(r.segments, other.segments)
|
|
for s0, s1 := itr.next(); s0 != nil || s1 != nil; s0, s1 = itr.next() {
|
|
// Use the other row's data if segment is missing.
|
|
if s0 == nil {
|
|
segments = append(segments, *s1)
|
|
continue
|
|
} else if s1 == nil {
|
|
segments = append(segments, *s0)
|
|
continue
|
|
}
|
|
|
|
// Otherwise merge.
|
|
s0.Merge(s1)
|
|
segments = append(segments, *s0)
|
|
}
|
|
|
|
r.segments = segments
|
|
r.invalidateCount()
|
|
}
|
|
|
|
// intersectionCount returns the number of intersections between r and other.
|
|
func (r *Row) intersectionCount(other *Row) uint64 {
|
|
var n uint64
|
|
|
|
itr := newMergeSegmentIterator(r.segments, other.segments)
|
|
for s0, s1 := itr.next(); s0 != nil || s1 != nil; s0, s1 = itr.next() {
|
|
// Ignore non-overlapping segments.
|
|
if s0 == nil || s1 == nil {
|
|
continue
|
|
}
|
|
|
|
n += s0.IntersectionCount(s1)
|
|
}
|
|
return n
|
|
}
|
|
|
|
// Intersect returns the itersection of r and other.
|
|
func (r *Row) Intersect(other *Row) *Row {
|
|
var segments []rowSegment
|
|
|
|
itr := newMergeSegmentIterator(r.segments, other.segments)
|
|
for s0, s1 := itr.next(); s0 != nil || s1 != nil; s0, s1 = itr.next() {
|
|
// Ignore non-overlapping segments.
|
|
if s0 == nil || s1 == nil {
|
|
continue
|
|
}
|
|
segments = append(segments, *s0.Intersect(s1))
|
|
}
|
|
|
|
return &Row{segments: segments}
|
|
}
|
|
|
|
// Any returns true if row contains any bits.
|
|
func (r *Row) Any() bool {
|
|
for _, s := range r.segments {
|
|
if s.data.Any() {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// Xor returns the xor of r and other.
|
|
func (r *Row) Xor(other *Row) *Row {
|
|
var segments []rowSegment
|
|
|
|
itr := newMergeSegmentIterator(r.segments, other.segments)
|
|
for s0, s1 := itr.next(); s0 != nil || s1 != nil; s0, s1 = itr.next() {
|
|
if s1 == nil {
|
|
segments = append(segments, *s0)
|
|
continue
|
|
} else if s0 == nil {
|
|
segments = append(segments, *s1)
|
|
continue
|
|
}
|
|
|
|
segments = append(segments, *s0.Xor(s1))
|
|
}
|
|
|
|
return &Row{segments: segments}
|
|
}
|
|
|
|
// Union returns the bitwise union of r and other.
|
|
func (r *Row) Union(others ...*Row) *Row {
|
|
segments := make([][]rowSegment, 0, len(others)+1)
|
|
if len(r.segments) > 0 {
|
|
segments = append(segments, r.segments)
|
|
}
|
|
nextSegs := make([][]rowSegment, 0, len(others)+1)
|
|
toProcess := make([]*rowSegment, 0, len(others)+1)
|
|
var output []rowSegment
|
|
for _, other := range others {
|
|
if len(other.segments) > 0 {
|
|
segments = append(segments, other.segments)
|
|
}
|
|
}
|
|
for len(segments) > 0 {
|
|
shard := segments[0][0].shard
|
|
for _, segs := range segments {
|
|
if segs[0].shard < shard {
|
|
shard = segs[0].shard
|
|
}
|
|
}
|
|
nextSegs = nextSegs[:0]
|
|
toProcess := toProcess[:0]
|
|
for _, segs := range segments {
|
|
if segs[0].shard == shard {
|
|
toProcess = append(toProcess, &segs[0])
|
|
segs = segs[1:]
|
|
}
|
|
if len(segs) > 0 {
|
|
nextSegs = append(nextSegs, segs)
|
|
}
|
|
}
|
|
// at this point, "toProcess" is a list of all the segments
|
|
// sharing the lowest ID, and nextSegs is a list of all the others.
|
|
// Swap the segment lists (so we don't have to reallocate it)
|
|
segments, nextSegs = nextSegs, segments
|
|
if len(toProcess) == 1 {
|
|
output = append(output, *toProcess[0])
|
|
} else {
|
|
output = append(output, *toProcess[0].Union(toProcess[1:]...))
|
|
}
|
|
}
|
|
return &Row{segments: output}
|
|
}
|
|
|
|
// GenericBinaryOp returns the output of a generic op on r and other.
|
|
func (r *Row) GenericBinaryOp(op ext.GenericBitmapOpBitmap, other *Row, args map[string]interface{}) *Row {
|
|
var segments []rowSegment
|
|
itr := newMergeSegmentIterator(r.segments, other.segments)
|
|
for s0, s1 := itr.next(); s0 != nil || s1 != nil; s0, s1 = itr.next() {
|
|
if s1 == nil {
|
|
segments = append(segments, *s0)
|
|
continue
|
|
} else if s0 == nil {
|
|
segments = append(segments, *s1)
|
|
continue
|
|
}
|
|
segments = append(segments, *s0.GenericBinaryOp(op, s1, args))
|
|
}
|
|
|
|
return &Row{segments: segments}
|
|
}
|
|
|
|
// GenericNaryOp returns the output of an nary op on r and others.
|
|
func (r *Row) GenericNaryOp(op ext.GenericBitmapOpBitmap, others []*Row, args map[string]interface{}) *Row {
|
|
segments := make([][]rowSegment, 0, len(others)+1)
|
|
if len(r.segments) > 0 {
|
|
segments = append(segments, r.segments)
|
|
}
|
|
nextSegs := make([][]rowSegment, 0, len(others)+1)
|
|
toProcess := make([]*rowSegment, 0, len(others)+1)
|
|
var output []rowSegment
|
|
for _, other := range others {
|
|
if len(other.segments) > 0 {
|
|
segments = append(segments, other.segments)
|
|
}
|
|
}
|
|
for len(segments) > 0 {
|
|
shard := segments[0][0].shard
|
|
for _, segs := range segments {
|
|
if segs[0].shard < shard {
|
|
shard = segs[0].shard
|
|
}
|
|
}
|
|
nextSegs = nextSegs[:0]
|
|
toProcess := toProcess[:0]
|
|
for _, segs := range segments {
|
|
if segs[0].shard == shard {
|
|
toProcess = append(toProcess, &segs[0])
|
|
segs = segs[1:]
|
|
}
|
|
if len(segs) > 0 {
|
|
nextSegs = append(nextSegs, segs)
|
|
}
|
|
}
|
|
// at this point, "toProcess" is a list of all the segments
|
|
// sharing the lowest ID, and nextSegs is a list of all the others.
|
|
// Swap the segment lists (so we don't have to reallocate it)
|
|
segments, nextSegs = nextSegs, segments
|
|
output = append(output, *toProcess[0].GenericNaryOp(op, toProcess[1:], args))
|
|
}
|
|
return &Row{segments: output}
|
|
}
|
|
|
|
// Difference returns the diff of r and other.
|
|
func (r *Row) Difference(other *Row) *Row {
|
|
var segments []rowSegment
|
|
|
|
itr := newMergeSegmentIterator(r.segments, other.segments)
|
|
for s0, s1 := itr.next(); s0 != nil || s1 != nil; s0, s1 = itr.next() {
|
|
if s0 == nil {
|
|
continue
|
|
} else if s1 == nil {
|
|
segments = append(segments, *s0)
|
|
continue
|
|
}
|
|
segments = append(segments, *s0.Difference(s1))
|
|
}
|
|
|
|
return &Row{segments: segments}
|
|
}
|
|
|
|
// GenericUnary returns the results of a generic op on r.
|
|
func (r *Row) GenericUnaryOp(op ext.GenericBitmapOpBitmap, args map[string]interface{}) *Row {
|
|
work := r
|
|
var segments []rowSegment
|
|
for _, segment := range work.segments {
|
|
opped := segment.GenericUnaryOp(op, args)
|
|
segments = append(segments, *opped)
|
|
}
|
|
return &Row{segments: segments}
|
|
}
|
|
|
|
// Shift returns the bitwise shift of r by n bits.
|
|
// Currently only positive shift values are supported.
|
|
func (r *Row) Shift(n int64) (*Row, error) {
|
|
if n < 0 {
|
|
return nil, errors.New("cannot shift by negative values")
|
|
} else if n == 0 {
|
|
return r, nil
|
|
}
|
|
|
|
work := r
|
|
var segments []rowSegment
|
|
for i := int64(0); i < n; i++ {
|
|
segments = segments[:0]
|
|
for _, segment := range work.segments {
|
|
shifted, err := segment.Shift()
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "shifting row segment")
|
|
}
|
|
segments = append(segments, *shifted)
|
|
}
|
|
work = &Row{segments: segments}
|
|
}
|
|
|
|
return work, nil
|
|
}
|
|
|
|
// SetBit sets the i-th column of the row.
|
|
func (r *Row) SetBit(i uint64) (changed bool) {
|
|
return r.createSegmentIfNotExists(i / ShardWidth).SetBit(i)
|
|
}
|
|
|
|
// Segments returns a list of all segments in the row.
|
|
func (r *Row) Segments() []rowSegment {
|
|
return r.segments
|
|
}
|
|
|
|
// segment returns a segment for a given shard.
|
|
// Returns nil if segment does not exist.
|
|
func (r *Row) segment(shard uint64) *rowSegment {
|
|
if i := sort.Search(len(r.segments), func(i int) bool {
|
|
return r.segments[i].shard >= shard
|
|
}); i < len(r.segments) && r.segments[i].shard == shard {
|
|
return &r.segments[i]
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (r *Row) createSegmentIfNotExists(shard uint64) *rowSegment {
|
|
i := sort.Search(len(r.segments), func(i int) bool {
|
|
return r.segments[i].shard >= shard
|
|
})
|
|
|
|
// Return exact match.
|
|
if i < len(r.segments) && r.segments[i].shard == shard {
|
|
return &r.segments[i]
|
|
}
|
|
|
|
// Insert new segment.
|
|
r.segments = append(r.segments, rowSegment{data: roaring.NewSliceBitmap()})
|
|
if i < len(r.segments) {
|
|
copy(r.segments[i+1:], r.segments[i:])
|
|
}
|
|
r.segments[i] = rowSegment{
|
|
data: roaring.NewSliceBitmap(),
|
|
shard: shard,
|
|
writable: true,
|
|
}
|
|
|
|
return &r.segments[i]
|
|
}
|
|
|
|
// invalidateCount updates the cached count in the row.
|
|
func (r *Row) invalidateCount() {
|
|
for i := range r.segments {
|
|
r.segments[i].InvalidateCount()
|
|
}
|
|
}
|
|
|
|
// Count returns the number of columns in the row.
|
|
func (r *Row) Count() uint64 {
|
|
var n uint64
|
|
for i := range r.segments {
|
|
n += r.segments[i].Count()
|
|
}
|
|
return n
|
|
}
|
|
|
|
// GenericCount applies an op to lots of things.
|
|
func (r *Row) GenericCount(op ext.BitmapOpUnaryCount, args map[string]interface{}) uint64 {
|
|
var n int64
|
|
for i := range r.segments {
|
|
n += op([]ext.Bitmap{WrapBitmap(r.segments[i].data)}, args)
|
|
}
|
|
return uint64(n)
|
|
}
|
|
|
|
// MarshalJSON returns a JSON-encoded byte slice of r.
|
|
func (r *Row) MarshalJSON() ([]byte, error) {
|
|
var o struct {
|
|
Attrs map[string]interface{} `json:"attrs"`
|
|
Columns []uint64 `json:"columns"`
|
|
Keys []string `json:"keys,omitempty"`
|
|
}
|
|
o.Columns = r.Columns()
|
|
o.Keys = r.Keys
|
|
|
|
o.Attrs = r.Attrs
|
|
if o.Attrs == nil {
|
|
o.Attrs = make(map[string]interface{})
|
|
}
|
|
|
|
return json.Marshal(&o)
|
|
}
|
|
|
|
// Columns returns the columns in r as a slice of ints.
|
|
func (r *Row) Columns() []uint64 {
|
|
a := make([]uint64, 0, r.Count())
|
|
for i := range r.segments {
|
|
a = append(a, r.segments[i].Columns()...)
|
|
}
|
|
return a
|
|
}
|
|
|
|
// Includes returns true if the row contains the given column.
|
|
func (r *Row) Includes(col uint64) bool {
|
|
// TODO: improve the efficiency of this method by
|
|
// performing the column filter at the bitmap level
|
|
// rather than iterating through the results here.
|
|
for i := range r.segments {
|
|
for _, c := range r.segments[i].Columns() {
|
|
if c == col {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// rowSegment holds a subset of a row.
|
|
// This could point to a mmapped roaring bitmap or an in-memory bitmap. The
|
|
// width of the segment will always match the shard width.
|
|
type rowSegment struct {
|
|
// Shard this segment belongs to
|
|
shard uint64
|
|
|
|
// Underlying raw bitmap implementation.
|
|
// This is an mmapped bitmap if writable is false. Otherwise
|
|
// it is a heap allocated bitmap which can be manipulated.
|
|
data *roaring.Bitmap
|
|
writable bool
|
|
|
|
// Bit count
|
|
n uint64
|
|
}
|
|
|
|
func (s *rowSegment) Freeze() {
|
|
s.data = s.data.Freeze()
|
|
}
|
|
|
|
/*
|
|
// Raw returns the row segment as a byte slice.
|
|
// It may be used by the gRPC server to deliver results
|
|
// as a roaring bitmap instead of a stream of RowResults.
|
|
func (s *rowSegment) Raw() (uint64, []byte) {
|
|
var buf bytes.Buffer
|
|
s.data.WriteTo(&buf)
|
|
return s.shard, buf.Bytes()
|
|
}
|
|
*/
|
|
|
|
// Merge adds chunks from other to s.
|
|
// Chunks in s are overwritten if they exist in other.
|
|
func (s *rowSegment) Merge(other *rowSegment) {
|
|
s.ensureWritable()
|
|
|
|
itr := other.data.Iterator()
|
|
for v, eof := itr.Next(); !eof; v, eof = itr.Next() {
|
|
s.SetBit(v)
|
|
}
|
|
}
|
|
|
|
// IntersectionCount returns the number of intersections between s and other.
|
|
func (s *rowSegment) IntersectionCount(other *rowSegment) uint64 {
|
|
return s.data.IntersectionCount(other.data)
|
|
}
|
|
|
|
// Intersect returns the itersection of s and other.
|
|
func (s *rowSegment) Intersect(other *rowSegment) *rowSegment {
|
|
data := s.data.Intersect(other.data)
|
|
data = data.Freeze()
|
|
|
|
return &rowSegment{
|
|
data: data,
|
|
shard: s.shard,
|
|
n: data.Count(),
|
|
writable: true,
|
|
}
|
|
}
|
|
|
|
// Union returns the bitwise union of s and other.
|
|
func (s *rowSegment) Union(others ...*rowSegment) *rowSegment {
|
|
datas := make([]*roaring.Bitmap, len(others))
|
|
for i, other := range others {
|
|
datas[i] = other.data
|
|
}
|
|
data := s.data.Union(datas...)
|
|
data.Freeze()
|
|
|
|
return &rowSegment{
|
|
data: data,
|
|
shard: s.shard,
|
|
n: data.Count(),
|
|
writable: true,
|
|
}
|
|
}
|
|
|
|
// GenericOp performs a generic op on s and other
|
|
func (s *rowSegment) GenericBinaryOp(op ext.GenericBitmapOpBitmap, other *rowSegment, args map[string]interface{}) *rowSegment {
|
|
data := op([]ext.Bitmap{WrapBitmap(s.data), WrapBitmap(other.data)}, args)
|
|
|
|
return &rowSegment{
|
|
data: UnwrapBitmap(data),
|
|
shard: s.shard,
|
|
n: data.Count(),
|
|
}
|
|
}
|
|
|
|
// GenericOp performs a generic op on s and others
|
|
func (s *rowSegment) GenericNaryOp(op ext.GenericBitmapOpBitmap, others []*rowSegment, args map[string]interface{}) *rowSegment {
|
|
bitmaps := make([]ext.Bitmap, len(others)+1)
|
|
bitmaps[0] = WrapBitmap(s.data)
|
|
for i, seg := range others {
|
|
bitmaps[i+1] = WrapBitmap(seg.data)
|
|
}
|
|
data := op(bitmaps, args)
|
|
|
|
return &rowSegment{
|
|
data: UnwrapBitmap(data),
|
|
shard: s.shard,
|
|
n: data.Count(),
|
|
}
|
|
}
|
|
|
|
// Difference returns the diff of s and other.
|
|
func (s *rowSegment) Difference(other *rowSegment) *rowSegment {
|
|
data := s.data.Difference(other.data)
|
|
data = data.Freeze()
|
|
|
|
return &rowSegment{
|
|
data: data,
|
|
shard: s.shard,
|
|
n: data.Count(),
|
|
writable: true,
|
|
}
|
|
}
|
|
|
|
// Xor returns the xor of s and other.
|
|
func (s *rowSegment) Xor(other *rowSegment) *rowSegment {
|
|
data := s.data.Xor(other.data)
|
|
data = data.Freeze()
|
|
|
|
return &rowSegment{
|
|
data: data,
|
|
shard: s.shard,
|
|
n: data.Count(),
|
|
writable: true,
|
|
}
|
|
}
|
|
|
|
// Shift returns s shifted by 1 bit.
|
|
func (s *rowSegment) Shift() (*rowSegment, error) {
|
|
//TODO deal with overflow
|
|
data, err := s.data.Shift(1)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "shifting roaring data")
|
|
}
|
|
data = data.Freeze()
|
|
|
|
return &rowSegment{
|
|
data: data,
|
|
shard: s.shard,
|
|
n: data.Count(),
|
|
writable: true,
|
|
}, nil
|
|
}
|
|
|
|
// GenericUnary returns s subject to op.
|
|
func (s *rowSegment) GenericUnaryOp(op ext.GenericBitmapOpBitmap, args map[string]interface{}) *rowSegment {
|
|
//TODO deal with overflow
|
|
data := UnwrapBitmap(op([]ext.Bitmap{WrapBitmap(s.data)}, args))
|
|
|
|
return &rowSegment{
|
|
data: data,
|
|
shard: s.shard,
|
|
n: data.Count(),
|
|
}
|
|
}
|
|
|
|
// SetBit sets the i-th column of the row.
|
|
func (s *rowSegment) SetBit(i uint64) (changed bool) {
|
|
s.ensureWritable()
|
|
changed, _ = s.data.Add(i)
|
|
if changed {
|
|
s.n++
|
|
}
|
|
return changed
|
|
}
|
|
|
|
// ClearBit clears the i-th column of the row.
|
|
func (s *rowSegment) ClearBit(i uint64) (changed bool) {
|
|
s.ensureWritable()
|
|
|
|
changed, _ = s.data.Remove(i)
|
|
if changed {
|
|
s.n--
|
|
}
|
|
return changed
|
|
}
|
|
|
|
// InvalidateCount updates the cached count in the row.
|
|
func (s *rowSegment) InvalidateCount() {
|
|
s.n = s.data.Count()
|
|
}
|
|
|
|
// Columns returns a list of all columns set in the segment.
|
|
func (s *rowSegment) Columns() []uint64 {
|
|
a := make([]uint64, 0, s.Count())
|
|
itr := s.data.Iterator()
|
|
for v, eof := itr.Next(); !eof; v, eof = itr.Next() {
|
|
a = append(a, v)
|
|
}
|
|
return a
|
|
}
|
|
|
|
// Count returns the number of set columns in the row.
|
|
func (s *rowSegment) Count() uint64 { return s.n }
|
|
|
|
// ensureWritable clones the segment if it is pointing to non-writable data.
|
|
func (s *rowSegment) ensureWritable() {
|
|
if s.writable {
|
|
return
|
|
}
|
|
|
|
// This doesn't actually clone all the containers, but does clone
|
|
// the bitmap itself -- we get a new bitmap, but it just marks the
|
|
// containers as frozen and shares them. It's now safe to write to
|
|
// this bitmap, but the actual containers are copy-on-write.
|
|
s.data = s.data.Freeze()
|
|
s.writable = true
|
|
}
|
|
|
|
// mergeSegmentIterator produces an iterator that loops through two sets of segments.
|
|
type mergeSegmentIterator struct {
|
|
a0, a1 []rowSegment
|
|
}
|
|
|
|
// newMergeSegmentIterator returns a new instance of mergeSegmentIterator.
|
|
func newMergeSegmentIterator(a0, a1 []rowSegment) mergeSegmentIterator {
|
|
return mergeSegmentIterator{a0: a0, a1: a1}
|
|
}
|
|
|
|
// next returns the next set of segments.
|
|
func (itr *mergeSegmentIterator) next() (s0, s1 *rowSegment) {
|
|
// Find current segments.
|
|
if len(itr.a0) > 0 {
|
|
s0 = &itr.a0[0]
|
|
}
|
|
if len(itr.a1) > 0 {
|
|
s1 = &itr.a1[0]
|
|
}
|
|
|
|
// Return if either or both are nil.
|
|
if s0 == nil && s1 == nil {
|
|
return
|
|
} else if s0 == nil {
|
|
itr.a1 = itr.a1[1:]
|
|
return
|
|
} else if s1 == nil {
|
|
itr.a0 = itr.a0[1:]
|
|
return
|
|
}
|
|
|
|
// Otherwise determine which is first.
|
|
if s0.shard < s1.shard {
|
|
itr.a0 = itr.a0[1:]
|
|
return s0, nil
|
|
} else if s0.shard > s1.shard {
|
|
itr.a1 = itr.a1[1:]
|
|
return s1, nil
|
|
}
|
|
|
|
// Return both if shards are equal.
|
|
itr.a0, itr.a1 = itr.a0[1:], itr.a1[1:]
|
|
return s0, s1
|
|
}
|