featurebase/tstore.go
tgruben 12e00c7949 Tstore PQL and rangeIterator (#2368)
* added varchar type; create table works; show columns works; wip on b+tree

* added page compaction on inserts where page has no free space

* fix bug with unpinning pages where depth of internal nodes > 1

* Now handling schema in it's own btree

* fixed some bugs

* null inserts, better tuple payload

* refactor tstore holdings

* refactor tstore management

* seekable read iterator compiles

* tstore rangeiteraor
Tstore pql command

* tstore rangeiteraor
Tstore pql command

* encoding/decoding support for TupleResults(Tstore)

* simplified range iterator

* add column filter for tstore

* bug fix

---------

Co-authored-by: pokeeffe-molecula <patrick.okeeffe@molecula.com>
2023-04-10 10:52:46 -05:00

265 lines
6.7 KiB
Go

// Copyright 2021 Molecula Corp. All rights reserved.
package pilosa
import (
"bytes"
"context"
"encoding/json"
"math"
"github.com/featurebasedb/featurebase/v3/pql"
"github.com/featurebasedb/featurebase/v3/sql3/planner/types"
"github.com/featurebasedb/featurebase/v3/tracing"
"github.com/featurebasedb/featurebase/v3/tstore"
"github.com/featurebasedb/featurebase/v3/wireprotocol"
"github.com/pkg/errors"
)
/*
The function Tstore provides filtered access to the point lookup values in tsore.
If Tstore is just provided a bitmap filter, such as ConstRow or any Bitmap Operation,
all the values associated with each column are returned. This set can be limited with
the addition of the header parameter
Example:
Tstore(ConstRow(columns=[2,4,6]),header=["fval"])
*/
func (e *executor) executeTstore(ctx context.Context, qcx *Qcx, index string, c *pql.Call, shards []uint64, opt *ExecOptions) (*TupleResults, error) {
span, ctx := tracing.StartSpanFromContext(ctx, "Executor.executeTstore")
defer span.Finish()
if len(c.Children) > 1 {
return nil, errors.New("Tstore() only accepts a single bitmap input filter")
}
var columnFilter []string
if cols, ok := c.Args["header"].([]interface{}); ok {
columnFilter = make([]string, 0, len(cols))
for _, v := range cols {
columnFilter = append(columnFilter, v.(string))
}
}
// Execute calls in bulk on each remote node and merge.
mapFn := func(ctx context.Context, shard uint64, mopt *mapOptions) (_ interface{}, err error) {
return e.executeTstoreShard(ctx, qcx, index, c, shard, columnFilter)
}
results := &TupleResults{}
reduceFn := func(ctx context.Context, prev, v interface{}) interface{} {
if v == nil {
return prev
}
r := v.(*TupleResults)
if results.TupleSchema == nil {
// just use the first one i get, they all should be the same
results.TupleSchema = r.TupleSchema
}
results.Append(r)
return nil
}
_, err := e.mapReduce(ctx, index, shards, c, opt, mapFn, reduceFn)
if err != nil {
return nil, err
}
return results, nil
}
func (e *executor) executeTstoreShard(ctx context.Context, qcx *Qcx, index string, c *pql.Call, shard uint64, columnFilter []string) (*TupleResults, error) {
span, _ := tracing.StartSpanFromContext(ctx, "Executor.executeArrowShard")
defer span.Finish()
var filter *Row
if len(c.Children) == 1 {
row, err := e.executeBitmapCallShard(ctx, qcx, index, c.Children[0], shard)
if err != nil {
return nil, err
}
filter = row
if !filter.Any() {
// no need to actuall run the query for its not operating against any values
return &TupleResults{}, nil
}
}
//
ids := filter.Columns() // needs to be shard columns
// Fetch index.
idx := e.Holder.Index(index)
if idx == nil {
return nil, newNotFoundError(ErrIndexNotFound, index)
}
b, err := idx.GetTStore(shard)
if err != nil {
return nil, err
}
first := tstore.Int(0)
last := tstore.Int(math.MaxInt32)
i := 0
in := func(a tstore.Sortable) bool {
if i >= len(ids) {
return false
}
for tstore.Int(ids[i]).Less(a) {
i++
if len(ids) == i {
return false
}
}
return true
}
if len(ids) == 0 {
in = func(a tstore.Sortable) bool {
return true
}
} else {
first = tstore.Int(ids[0])
last = tstore.Int(ids[len(ids)-1] + 1)
}
itr, err := b.NewRangeIterator(tstore.Int(first), tstore.Int(last))
defer itr.Dispose()
result := &TupleResults{}
unset := true
fixTuple := func(tuple *tstore.BTreeTuple) types.Row {
return tuple.Tuple
}
for itr.Next() {
item, key := itr.Item()
if unset {
result.TupleSchema = item.TupleSchema
if len(columnFilter) > 0 {
newSchema := make(types.Schema, 0)
parts := make([]int, 0)
for _, name := range columnFilter {
for i := range item.TupleSchema {
if item.TupleSchema[i].ColumnName == name {
newSchema = append(newSchema, item.TupleSchema[i])
parts = append(parts, i)
}
}
}
if len(newSchema) == 0 {
return &TupleResults{}, nil
}
fixTuple = func(tuple *tstore.BTreeTuple) types.Row {
newrow := make(types.Row, len(parts))
for i, v := range parts {
newrow[i] = tuple.Tuple[v]
}
return newrow
}
result.TupleSchema = newSchema
}
unset = false
}
if in(key) {
result.Add(fixTuple(item))
}
}
return result, nil
}
type TupleResults struct {
TupleSchema types.Schema
rows []types.Row
}
func (tr *TupleResults) Add(item types.Row) {
tr.rows = append(tr.rows, item)
}
func (tr *TupleResults) Append(t *TupleResults) {
tr.rows = append(tr.rows, t.rows...)
}
func (tr *TupleResults) MarshalJSON() ([]byte, error) {
results := make(map[string]interface{})
columns := make([][]string, 0)
for i := range tr.TupleSchema {
column := tr.TupleSchema[i]
columns = append(columns, []string{column.ColumnName, column.Type.BaseTypeName()})
}
rows := make([][]interface{}, 0)
for i := range tr.rows {
item := tr.rows[i]
row := make([]interface{}, len(columns))
for c := range item {
row[c] = item[c]
}
rows = append(rows, row)
}
results["schema"] = columns
results["rows"] = rows
return json.Marshal(results)
}
func (tr *TupleResults) ToBytes() ([]byte, error) {
buf := new(bytes.Buffer)
// get the bytes for the schema
b, err := wireprotocol.WriteSchema(tr.TupleSchema)
if err != nil {
return nil, errors.Wrap(err, "serializing tuple schema")
}
_, err = buf.Write(b)
if err != nil {
return nil, errors.Wrap(err, "serializing tuple schema")
}
// iterate the tupleData - outside loop is rows
for _, trow := range tr.rows {
rb, err := wireprotocol.WriteRow(trow, tr.TupleSchema)
if err != nil {
return nil, errors.Wrap(err, "serializing tuple row")
}
_, err = buf.Write(rb)
if err != nil {
return nil, errors.Wrap(err, "serializing tuple row")
}
}
// write done to the buffer
b = wireprotocol.WriteDone()
_, err = buf.Write(b)
if err != nil {
return nil, errors.Wrap(err, "serializing tuples")
}
return buf.Bytes(), nil
}
func NewTupleResultFromBytes(data []byte) (*TupleResults, error) {
rdr := bytes.NewReader(data)
_, err := wireprotocol.ExpectToken(rdr, wireprotocol.TOKEN_SCHEMA_INFO)
if err != nil {
return nil, err
}
// get the row schema from the import data
schema, err := wireprotocol.ReadSchema(rdr)
if err != nil {
return nil, err
}
// read rows until we get to the end
tk, err := wireprotocol.ReadToken(rdr)
if err != nil {
return nil, err
}
rows := make([]types.Row, 0)
for tk == wireprotocol.TOKEN_ROW {
row, err := wireprotocol.ReadRow(rdr, schema)
if err != nil {
return nil, err
}
rows = append(rows, row)
tk, err = wireprotocol.ReadToken(rdr)
if err != nil {
return nil, err
}
}
if tk != wireprotocol.TOKEN_DONE {
return nil, errors.Errorf("unexpected token '%d'", tk)
}
return &TupleResults{TupleSchema: schema, rows: rows}, nil
}