mirror of
https://github.com/featurebasedb/featurebase.git
synced 2026-08-28 10:54:59 +00:00
- introduce Query Context (Qcx) for managing database-per-shard. - replaces the MultiTx, so mtx.go is retired and removed. - introduces the HolderConfig struct and all Holders now have a path from birth. - rbf speedups on bitwise writes - badgerdb is removed due to unresolvable write conflicts. fixes #703 #676
6359 lines
174 KiB
Go
6359 lines
174 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 (
|
||
"context"
|
||
"encoding/json"
|
||
"fmt"
|
||
"math"
|
||
"math/bits"
|
||
"sort"
|
||
"strings"
|
||
"sync"
|
||
"time"
|
||
|
||
"github.com/pilosa/pilosa/v2/pql"
|
||
pb "github.com/pilosa/pilosa/v2/proto"
|
||
"github.com/pilosa/pilosa/v2/roaring"
|
||
"github.com/pilosa/pilosa/v2/shardwidth"
|
||
"github.com/pilosa/pilosa/v2/testhook"
|
||
"github.com/pilosa/pilosa/v2/tracing"
|
||
"github.com/pkg/errors"
|
||
)
|
||
|
||
// defaultField is the field used if one is not specified.
|
||
const (
|
||
defaultField = "general"
|
||
|
||
// defaultMinThreshold is the lowest count to use in a Top-N operation when
|
||
// looking for additional id/count pairs.
|
||
defaultMinThreshold = 1
|
||
|
||
columnLabel = "col"
|
||
rowLabel = "row"
|
||
)
|
||
|
||
// executor recursively executes calls in a PQL query across all shards.
|
||
type executor struct {
|
||
Holder *Holder
|
||
|
||
// Local hostname & cluster configuration.
|
||
Node *Node
|
||
Cluster *cluster
|
||
|
||
// Client used for remote requests.
|
||
client InternalQueryClient
|
||
|
||
// Maximum number of Set() or Clear() commands per request.
|
||
MaxWritesPerRequest int
|
||
|
||
shutdown bool
|
||
workMu sync.RWMutex
|
||
workersWG sync.WaitGroup
|
||
workerPoolSize int
|
||
work chan job
|
||
}
|
||
|
||
// executorOption is a functional option type for pilosa.Executor
|
||
type executorOption func(e *executor) error
|
||
|
||
func optExecutorInternalQueryClient(c InternalQueryClient) executorOption {
|
||
return func(e *executor) error {
|
||
e.client = c
|
||
return nil
|
||
}
|
||
}
|
||
|
||
func optExecutorWorkerPoolSize(size int) executorOption {
|
||
return func(e *executor) error {
|
||
e.workerPoolSize = size
|
||
return nil
|
||
}
|
||
}
|
||
|
||
func emptyResult(c *pql.Call) interface{} {
|
||
switch c.Name {
|
||
case "Clear", "ClearRow":
|
||
return false
|
||
|
||
case "Row":
|
||
return &Row{Keys: []string{}}
|
||
|
||
case "Rows":
|
||
return RowIdentifiers{Keys: []string{}}
|
||
|
||
case "IncludesColumn":
|
||
return false
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// newExecutor returns a new instance of Executor.
|
||
func newExecutor(opts ...executorOption) *executor {
|
||
e := &executor{
|
||
client: newNopInternalQueryClient(),
|
||
workerPoolSize: 2,
|
||
}
|
||
for _, opt := range opts {
|
||
err := opt(e)
|
||
if err != nil {
|
||
panic(err)
|
||
}
|
||
}
|
||
// this channel cap doesn't necessarily have to be the same as
|
||
// workerPoolSize... any larger doesn't seem to have an effect in
|
||
// the few tests we've done at scale with concurrent query
|
||
// workloads. Possible that it could be smaller.
|
||
e.work = make(chan job, e.workerPoolSize)
|
||
_ = testhook.Opened(NewAuditor(), e, nil)
|
||
for i := 0; i < e.workerPoolSize; i++ {
|
||
e.workersWG.Add(1)
|
||
go func() {
|
||
defer e.workersWG.Done()
|
||
worker(e.work)
|
||
}()
|
||
}
|
||
return e
|
||
}
|
||
|
||
func (e *executor) Close() error {
|
||
e.workMu.Lock()
|
||
defer e.workMu.Unlock()
|
||
e.shutdown = true
|
||
_ = testhook.Closed(NewAuditor(), e, nil)
|
||
close(e.work)
|
||
e.workersWG.Wait()
|
||
return nil
|
||
}
|
||
|
||
// Execute executes a PQL query.
|
||
func (e *executor) Execute(ctx context.Context, index string, q *pql.Query, shards []uint64, opt *execOptions) (QueryResponse, error) {
|
||
span, ctx := tracing.StartSpanFromContext(ctx, "Executor.Execute")
|
||
span.LogKV("pql", q.String())
|
||
defer span.Finish()
|
||
|
||
resp := QueryResponse{}
|
||
|
||
// Check for query cancellation.
|
||
if err := validateQueryContext(ctx); err != nil {
|
||
return resp, err
|
||
}
|
||
|
||
// Verify that an index is set.
|
||
if index == "" {
|
||
return resp, ErrIndexRequired
|
||
}
|
||
|
||
idx := e.Holder.Index(index)
|
||
if idx == nil {
|
||
return resp, newNotFoundError(ErrIndexNotFound, index)
|
||
}
|
||
|
||
needWriteTxn := false
|
||
nw := q.WriteCallN()
|
||
if nw > 0 {
|
||
needWriteTxn = true
|
||
}
|
||
|
||
// Verify that the number of writes do not exceed the maximum.
|
||
if e.MaxWritesPerRequest > 0 && nw > e.MaxWritesPerRequest {
|
||
return resp, ErrTooManyWrites
|
||
}
|
||
|
||
// Default options.
|
||
if opt == nil {
|
||
opt = &execOptions{}
|
||
}
|
||
|
||
// Translate query keys to ids, if necessary.
|
||
// No need to translate a remote call.
|
||
if !opt.Remote {
|
||
if err := e.translateCalls(ctx, index, q.Calls); err != nil {
|
||
if errors.Cause(err) == ErrTranslatingKeyNotFound {
|
||
// No error - return empty result
|
||
resp.Results = make([]interface{}, len(q.Calls))
|
||
for i, c := range q.Calls {
|
||
resp.Results[i] = emptyResult(c)
|
||
}
|
||
return resp, nil
|
||
}
|
||
return resp, err
|
||
} else if err := validateQueryContext(ctx); err != nil {
|
||
return resp, err
|
||
}
|
||
}
|
||
if opt.Profile {
|
||
var prof tracing.ProfiledSpan
|
||
prof, ctx = tracing.StartProfiledSpanFromContext(ctx, "Execute")
|
||
defer prof.Finish()
|
||
var ok bool
|
||
resp.Profile, ok = prof.(*tracing.Profile)
|
||
if !ok {
|
||
return resp, fmt.Errorf("profiling execution failed: %T is not tracing.Profile", prof)
|
||
}
|
||
}
|
||
|
||
// Can't do NewTx() this high up, because we need a specific shard.
|
||
// So start a ccx with a TxGroup and pass it down.
|
||
qcx := idx.holder.txf.NewQcx()
|
||
defer qcx.Abort()
|
||
|
||
results, err := e.execute(ctx, qcx, index, q, shards, opt)
|
||
if err != nil {
|
||
return resp, err
|
||
} else if err := validateQueryContext(ctx); err != nil {
|
||
return resp, err
|
||
}
|
||
resp.Results = results
|
||
|
||
// Fill column attributes if requested.
|
||
if opt.ColumnAttrs {
|
||
// Consolidate all column ids across all calls.
|
||
var columnIDs []uint64
|
||
for _, result := range results {
|
||
bm, ok := result.(*Row)
|
||
if !ok {
|
||
continue
|
||
}
|
||
columnIDs = uint64Slice(columnIDs).merge(bm.Columns())
|
||
}
|
||
|
||
// Retrieve column attributes across all calls.
|
||
columnAttrSets, err := e.readColumnAttrSets(e.Holder.Index(index), columnIDs)
|
||
if err != nil {
|
||
return resp, errors.Wrap(err, "reading column attrs")
|
||
}
|
||
|
||
// Translate column attributes, if necessary.
|
||
if idx.Keys() {
|
||
idSet := make(map[uint64]struct{})
|
||
for _, col := range columnAttrSets {
|
||
idSet[col.ID] = struct{}{}
|
||
}
|
||
|
||
idMap, err := e.Cluster.translateIndexIDSet(ctx, index, idSet)
|
||
if err != nil {
|
||
return resp, errors.Wrap(err, "translating id set")
|
||
}
|
||
|
||
for _, col := range columnAttrSets {
|
||
col.Key, col.ID = idMap[col.ID], 0
|
||
}
|
||
}
|
||
|
||
resp.ColumnAttrSets = columnAttrSets
|
||
}
|
||
|
||
// Translate response objects from ids to keys, if necessary.
|
||
// No need to translate a remote call.
|
||
if !opt.Remote {
|
||
// only translateResults if this local node is the final destination. only string/column keys.
|
||
if err := e.translateResults(ctx, index, idx, q.Calls, results); err != nil {
|
||
if errors.Cause(err) == ErrTranslatingKeyNotFound {
|
||
// No error - return empty result
|
||
resp.Results = make([]interface{}, len(q.Calls))
|
||
for i, c := range q.Calls {
|
||
resp.Results[i] = emptyResult(c)
|
||
}
|
||
return resp, nil
|
||
}
|
||
return resp, err
|
||
} else if err := validateQueryContext(ctx); err != nil {
|
||
return resp, err
|
||
}
|
||
}
|
||
// Must copy out of Tx data before Commiting, because it will become invalid afterwards.
|
||
respSafeNoTxData := e.safeCopy(resp)
|
||
|
||
// Commit transactions if writing; else let the defer grp.Abort do the rollbacks.
|
||
if needWriteTxn {
|
||
if err := qcx.Finish(); err != nil {
|
||
return respSafeNoTxData, err
|
||
}
|
||
}
|
||
return respSafeNoTxData, nil
|
||
}
|
||
|
||
// safeCopy copies everything in resp that has Bitmap material,
|
||
// to avoid anything coming from the mmap-ed Tx storage.
|
||
func (e *executor) safeCopy(resp QueryResponse) (out QueryResponse) {
|
||
out = QueryResponse{
|
||
// not transactional, from attribute storage so no need to clone these:
|
||
ColumnAttrSets: resp.ColumnAttrSets, // []*ColumnAttrSet
|
||
Err: resp.Err, // error
|
||
Profile: resp.Profile, // *tracing.Profile
|
||
}
|
||
// Results can contain *roaring.Bitmap, so need to copy from Tx mmap-ed memory.
|
||
for _, v := range resp.Results {
|
||
switch x := v.(type) {
|
||
case *Row:
|
||
rowSafe := x.Clone()
|
||
out.Results = append(out.Results, rowSafe)
|
||
case bool:
|
||
out.Results = append(out.Results, x)
|
||
case nil:
|
||
out.Results = append(out.Results, nil)
|
||
case uint64:
|
||
out.Results = append(out.Results, x) // for counts
|
||
case *PairsField:
|
||
// no bitmap material, so should be ok to skip Clone()
|
||
out.Results = append(out.Results, x)
|
||
case PairField: // not PairsField but PairField
|
||
// no bitmap material, so should be ok to skip Clone()
|
||
out.Results = append(out.Results, x)
|
||
case ValCount:
|
||
// no bitmap material, so should be ok to skip Clone()
|
||
out.Results = append(out.Results, x)
|
||
case SignedRow:
|
||
// has *Row in it, so has Bitmap material, and very likely needs Clone.
|
||
y := x.Clone()
|
||
out.Results = append(out.Results, *y)
|
||
case GroupCount:
|
||
// no bitmap material, so should be ok to skip Clone()
|
||
out.Results = append(out.Results, x)
|
||
case []GroupCount:
|
||
out.Results = append(out.Results, x)
|
||
case ExtractedTable:
|
||
out.Results = append(out.Results, x)
|
||
case ExtractedIDMatrix:
|
||
out.Results = append(out.Results, x)
|
||
case RowIdentifiers:
|
||
// no bitmap material, so should be ok to skip Clone()
|
||
out.Results = append(out.Results, x)
|
||
case RowIDs:
|
||
// defined as: type RowIDs []uint64
|
||
// so does not contain bitmap material, and
|
||
// should not need to be cloned.
|
||
out.Results = append(out.Results, x)
|
||
default:
|
||
panic(fmt.Sprintf("handle %T here", v))
|
||
}
|
||
}
|
||
return
|
||
}
|
||
|
||
// readColumnAttrSets returns a list of column attribute objects by id.
|
||
func (e *executor) readColumnAttrSets(index *Index, ids []uint64) ([]*ColumnAttrSet, error) {
|
||
if index == nil {
|
||
return nil, nil
|
||
}
|
||
|
||
ax := make([]*ColumnAttrSet, 0, len(ids))
|
||
for _, id := range ids {
|
||
// Read attributes for column. Skip column if empty.
|
||
attrs, err := index.ColumnAttrStore().Attrs(id)
|
||
if err != nil {
|
||
return nil, errors.Wrap(err, "getting attrs")
|
||
} else if len(attrs) == 0 {
|
||
continue
|
||
}
|
||
|
||
// Append column with attributes.
|
||
ax = append(ax, &ColumnAttrSet{ID: id, Attrs: attrs})
|
||
}
|
||
|
||
return ax, nil
|
||
}
|
||
|
||
// handlePreCalls traverses the call tree looking for calls that need
|
||
// precomputed values. Right now, that's just Distinct.
|
||
func (e *executor) handlePreCalls(ctx context.Context, qcx *Qcx, index string, c *pql.Call, shards []uint64, opt *execOptions) error {
|
||
if c.Name == "Precomputed" {
|
||
idx := c.Args["valueidx"].(int64)
|
||
if idx >= 0 && idx < int64(len(opt.EmbeddedData)) {
|
||
row := opt.EmbeddedData[idx]
|
||
c.Precomputed = make(map[uint64]interface{}, len(row.segments))
|
||
for _, segment := range row.segments {
|
||
c.Precomputed[segment.shard] = &Row{segments: []rowSegment{segment}}
|
||
}
|
||
} else {
|
||
return fmt.Errorf("no precomputed data! index %d, len %d", idx, len(opt.EmbeddedData))
|
||
}
|
||
return nil
|
||
}
|
||
newIndex := c.CallIndex()
|
||
// A cross-index query is handled by precall. This is inefficient,
|
||
// but we have to do it for now because shards might be different and
|
||
// we haven't implemented the local precalls that would be enough
|
||
// in some cases.
|
||
//
|
||
// This makes simple cross-index queries noticably inefficient.
|
||
//
|
||
// If you're here because of that: We should be using PrecallLocal
|
||
// in cases where the call isn't already PrecallGlobal, and
|
||
// PrecallLocal should wait until we're running on a specific node
|
||
// to do the farming-out of just the sub-queries it has to run
|
||
// for its local shards.
|
||
//
|
||
// As is, we have one node querying every node, then sending out
|
||
// all the data to every node, including the data that node already
|
||
// has. We could reduce the actual copying around dramatically,
|
||
// but only in the cases where local is good enough -- not something
|
||
// like Distinct, where you can't predict output shard for a result
|
||
// from the shard being queried.
|
||
if newIndex != "" && newIndex != index {
|
||
c.Type = pql.PrecallGlobal
|
||
index = newIndex
|
||
// we need to recompute shards, then
|
||
shards = nil
|
||
}
|
||
if err := e.handlePreCallChildren(ctx, qcx, index, c, shards, opt); err != nil {
|
||
return err
|
||
}
|
||
// child calls already handled, no precall for this, so we're done
|
||
if c.Type == pql.PrecallNone {
|
||
return nil
|
||
}
|
||
// We don't try to handle sub-calls from here. I'm not 100%
|
||
// sure that's right, but I think the fact that they're happening
|
||
// inside a precomputed call may mean they need different
|
||
// handling. In any event, the sub-calls will get handled by
|
||
// the executeCall when it gets to them...
|
||
|
||
// We set c to look like a normal call, and actually execute it:
|
||
c.Type = pql.PrecallNone
|
||
// possibly override call index.
|
||
v, err := e.executeCall(ctx, qcx, index, c, shards, opt)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
var row *Row
|
||
switch r := v.(type) {
|
||
case *Row:
|
||
row = r
|
||
case SignedRow:
|
||
row = r.Pos
|
||
default:
|
||
return fmt.Errorf("precomputed call %s returned unexpected non-Row data: %T", c.Name, v)
|
||
}
|
||
if err := ctx.Err(); err != nil {
|
||
return err
|
||
}
|
||
c.Children = []*pql.Call{}
|
||
c.Name = "Precomputed"
|
||
c.Args = map[string]interface{}{"valueidx": len(opt.EmbeddedData)}
|
||
// stash a copy of the full results, which can be forwarded to other
|
||
// shards if the query has to go to them
|
||
opt.EmbeddedData = append(opt.EmbeddedData, row)
|
||
// and stash a copy locally, so local calls can use it
|
||
c.Precomputed = make(map[uint64]interface{}, len(row.segments))
|
||
for _, segment := range row.segments {
|
||
c.Precomputed[segment.shard] = &Row{segments: []rowSegment{segment}}
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// dumpPrecomputedCalls throws away precomputed call data. this is used so we
|
||
// can drop any large data associated with a call once we've processed
|
||
// the call.
|
||
func (e *executor) dumpPrecomputedCalls(ctx context.Context, c *pql.Call) {
|
||
for _, call := range c.Children {
|
||
e.dumpPrecomputedCalls(ctx, call)
|
||
}
|
||
c.Precomputed = nil
|
||
}
|
||
|
||
// handlePreCallChildren handles any pre-calls in the children of a given call.
|
||
func (e *executor) handlePreCallChildren(ctx context.Context, qcx *Qcx, index string, c *pql.Call, shards []uint64, opt *execOptions) error {
|
||
for i := range c.Children {
|
||
if err := ctx.Err(); err != nil {
|
||
return err
|
||
}
|
||
if err := e.handlePreCalls(ctx, qcx, index, c.Children[i], shards, opt); err != nil {
|
||
return err
|
||
}
|
||
}
|
||
for _, val := range c.Args {
|
||
// Handle Call() operations which exist inside named arguments, too.
|
||
if call, ok := val.(*pql.Call); ok {
|
||
if err := ctx.Err(); err != nil {
|
||
return err
|
||
}
|
||
if err := e.handlePreCalls(ctx, qcx, index, call, shards, opt); err != nil {
|
||
return err
|
||
}
|
||
}
|
||
}
|
||
return nil
|
||
}
|
||
|
||
func (e *executor) execute(ctx context.Context, qcx *Qcx, index string, q *pql.Query, shards []uint64, opt *execOptions) ([]interface{}, error) {
|
||
span, ctx := tracing.StartSpanFromContext(ctx, "Executor.execute")
|
||
defer span.Finish()
|
||
|
||
// Don't bother calculating shards for query types that don't require it.
|
||
needsShards := needsShards(q.Calls)
|
||
|
||
// If shards are specified, then use that value for shards. If shards aren't
|
||
// specified, then include all of them.
|
||
if len(shards) == 0 && needsShards {
|
||
// Round up the number of shards.
|
||
idx := e.Holder.Index(index)
|
||
if idx == nil {
|
||
return nil, newNotFoundError(ErrIndexNotFound, index)
|
||
}
|
||
shards = idx.AvailableShards().Slice()
|
||
if len(shards) == 0 {
|
||
shards = []uint64{0}
|
||
}
|
||
}
|
||
|
||
// Optimize handling for bulk attribute insertion.
|
||
if hasOnlySetRowAttrs(q.Calls) {
|
||
return e.executeBulkSetRowAttrs(ctx, qcx, index, q.Calls, opt)
|
||
}
|
||
|
||
// Execute each call serially.
|
||
results := make([]interface{}, 0, len(q.Calls))
|
||
for i, call := range q.Calls {
|
||
|
||
if err := validateQueryContext(ctx); err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
// If you actually make a top-level Distinct call, you
|
||
// want a SignedRow back. Otherwise, it's something else
|
||
// that will be using it as a row, and we only care
|
||
// about the positive values, because only positive values
|
||
// are valid column IDs. So we don't actually eat top-level
|
||
// pre calls.
|
||
err := e.handlePreCallChildren(ctx, qcx, index, call, shards, opt)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
var v interface{}
|
||
// Top-level calls don't need to precompute cross-index things,
|
||
// because we can just pick whatever index we want, but we
|
||
// still need to handle them. Since everything else was
|
||
// already precomputed by handlePreCallChildren, though,
|
||
// we don't need this logic in executeCall.
|
||
newIndex := call.CallIndex()
|
||
if newIndex != "" && newIndex != index {
|
||
v, err = e.executeCall(ctx, qcx, newIndex, call, nil, opt)
|
||
} else {
|
||
v, err = e.executeCall(ctx, qcx, index, call, shards, opt)
|
||
}
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
results = append(results, v)
|
||
// Some Calls can have significant data associated with them
|
||
// that gets generated during processing, such as Precomputed
|
||
// values. Dumping the precomputed data, if any, lets the GC
|
||
// free the memory before we get there.
|
||
e.dumpPrecomputedCalls(ctx, q.Calls[i])
|
||
}
|
||
return results, nil
|
||
}
|
||
|
||
// preprocessQuery expands any calls that need preprocessing.
|
||
func (e *executor) preprocessQuery(ctx context.Context, qcx *Qcx, index string, c *pql.Call, shards []uint64, opt *execOptions) (*pql.Call, error) {
|
||
switch c.Name {
|
||
case "UnionRows":
|
||
// Turn UnionRows(Rows(...)) into Union(Row(...), ...).
|
||
var rows []*pql.Call
|
||
for _, child := range c.Children {
|
||
// Check that we can use the call.
|
||
switch child.Name {
|
||
case "Rows":
|
||
case "TopN":
|
||
default:
|
||
return nil, errors.Errorf("cannot use %v as a rows query", child)
|
||
}
|
||
|
||
// Execute the call.
|
||
rowsResult, err := e.executeCall(ctx, qcx, index, child, shards, opt)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
// Turn the results into rows calls.
|
||
var resultRows []*pql.Call
|
||
switch rowsResult := rowsResult.(type) {
|
||
case *PairsField:
|
||
// Translate pairs into rows calls.
|
||
for _, p := range rowsResult.Pairs {
|
||
var val interface{}
|
||
switch {
|
||
case p.Key != "":
|
||
val = p.Key
|
||
default:
|
||
val = p.ID
|
||
}
|
||
resultRows = append(resultRows, &pql.Call{
|
||
Name: "Row",
|
||
Args: map[string]interface{}{
|
||
rowsResult.Field: val,
|
||
},
|
||
})
|
||
}
|
||
case RowIDs:
|
||
// Translate Row IDs into Row calls.
|
||
for _, id := range rowsResult {
|
||
resultRows = append(resultRows, &pql.Call{
|
||
Name: "Row",
|
||
Args: map[string]interface{}{
|
||
child.Args["_field"].(string): id,
|
||
},
|
||
})
|
||
}
|
||
default:
|
||
return nil, errors.Errorf("unexpected Rows type %T", rowsResult)
|
||
}
|
||
|
||
// Propogate any special properties of the call.
|
||
switch child.Name {
|
||
case "Rows":
|
||
// Propogate "from" time, if set.
|
||
if v, ok := child.Args["from"]; ok {
|
||
for _, rowCall := range resultRows {
|
||
rowCall.Args["from"] = v
|
||
}
|
||
}
|
||
|
||
// Propogate "to" time, if set.
|
||
if v, ok := child.Args["to"]; ok {
|
||
for _, rowCall := range resultRows {
|
||
rowCall.Args["to"] = v
|
||
}
|
||
}
|
||
}
|
||
|
||
rows = append(rows, resultRows...)
|
||
}
|
||
|
||
// Generate a Union call over the rows.
|
||
return &pql.Call{
|
||
Name: "Union",
|
||
Children: rows,
|
||
}, nil
|
||
|
||
case "ConstRow":
|
||
// Fetch user-provided columns list.
|
||
cols, _ := c.Args["columns"].([]interface{})
|
||
var ids []uint64
|
||
var keys []string
|
||
for _, c := range cols {
|
||
switch c := c.(type) {
|
||
case uint64:
|
||
ids = append(ids, c)
|
||
case int64:
|
||
ids = append(ids, uint64(c))
|
||
case string:
|
||
keys = append(keys, c)
|
||
default:
|
||
return nil, errors.Errorf("invalid column identifier %v of type %T", c, c)
|
||
}
|
||
}
|
||
|
||
// Translate keys to IDs.
|
||
if len(keys) > 0 {
|
||
keyIDs, err := e.Cluster.translateIndexKeys(ctx, index, keys, false)
|
||
if err != nil {
|
||
return nil, errors.Wrap(err, "translating column IDs in ConstRow")
|
||
}
|
||
ids = append(ids, keyIDs...)
|
||
}
|
||
|
||
// Split IDs by shard.
|
||
shardSet := make(map[uint64][]uint64)
|
||
for _, id := range ids {
|
||
shardSet[id/ShardWidth] = append(shardSet[id/ShardWidth], id)
|
||
}
|
||
|
||
// Convert ID sets to per-shard Row objects.
|
||
precomputed := make(map[uint64]interface{})
|
||
for _, s := range shards {
|
||
precomputed[s] = NewRow(shardSet[s]...)
|
||
}
|
||
|
||
// Generate a precomputed call with the data.
|
||
return &pql.Call{
|
||
Name: "Precomputed",
|
||
Precomputed: precomputed,
|
||
}, nil
|
||
|
||
case "All":
|
||
_, hasLimit, err := c.UintArg("limit")
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
_, hasOffset, err := c.UintArg("offset")
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
if !hasLimit && !hasOffset {
|
||
return c, nil
|
||
}
|
||
|
||
// Rewrite the All() w/ limit to Limit(All()).
|
||
c.Children = []*pql.Call{
|
||
{
|
||
Name: "All",
|
||
},
|
||
}
|
||
c.Name = "Limit"
|
||
fallthrough
|
||
|
||
case "Limit":
|
||
if len(c.Children) != 1 {
|
||
return nil, errors.Errorf("expected 1 child of limit call but got %d", len(c.Children))
|
||
}
|
||
res, err := e.preprocessQuery(ctx, qcx, index, c.Children[0], shards, opt)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
c.Children[0] = res
|
||
err = e.executeLimitCall(ctx, qcx, index, c, shards, opt)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
return c, nil
|
||
|
||
default:
|
||
// Recurse through child calls.
|
||
out := make([]*pql.Call, len(c.Children))
|
||
var changed bool
|
||
for i, child := range c.Children {
|
||
res, err := e.preprocessQuery(ctx, qcx, index, child, shards, opt)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
if res != child {
|
||
changed = true
|
||
}
|
||
out[i] = res
|
||
}
|
||
if changed {
|
||
c = c.Clone()
|
||
c.Children = out
|
||
}
|
||
return c, nil
|
||
}
|
||
}
|
||
|
||
// executeCall executes a call.
|
||
func (e *executor) executeCall(ctx context.Context, qcx *Qcx, index string, c *pql.Call, shards []uint64, opt *execOptions) (interface{}, error) {
|
||
span, ctx := tracing.StartSpanFromContext(ctx, "Executor.executeCall")
|
||
defer span.Finish()
|
||
|
||
if err := validateQueryContext(ctx); err != nil {
|
||
return nil, err
|
||
} else if err := e.validateCallArgs(c); err != nil {
|
||
return nil, errors.Wrap(err, "validating args")
|
||
}
|
||
indexTag := "index:" + index
|
||
metricName := "query_" + strings.ToLower(c.Name) + "_total"
|
||
statFn := func() {
|
||
if !opt.Remote {
|
||
e.Holder.Stats.CountWithCustomTags(metricName, 1, 1.0, []string{indexTag})
|
||
}
|
||
}
|
||
|
||
// Fixes #2009
|
||
// See: https://github.com/pilosa/pilosa/issues/2009
|
||
// TODO: Remove at version 2.0
|
||
if e.detectRangeCall(c) {
|
||
e.Holder.Logger.Printf("DEPRECATED: Range() is deprecated, please use Row() instead.")
|
||
}
|
||
|
||
// If shards are specified, then use that value for shards. If shards aren't
|
||
// specified, then include all of them.
|
||
if shards == nil && needsShards([]*pql.Call{c}) {
|
||
// Round up the number of shards.
|
||
idx := e.Holder.Index(index)
|
||
if idx == nil {
|
||
return nil, newNotFoundError(ErrIndexNotFound, index)
|
||
}
|
||
shards = idx.AvailableShards().Slice()
|
||
if len(shards) == 0 {
|
||
shards = []uint64{0}
|
||
}
|
||
}
|
||
|
||
// Preprocess the query.
|
||
c, err := e.preprocessQuery(ctx, qcx, index, c, shards, opt)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
switch c.Name {
|
||
case "Sum":
|
||
statFn()
|
||
return e.executeSum(ctx, qcx, index, c, shards, opt)
|
||
case "Min":
|
||
statFn()
|
||
return e.executeMin(ctx, qcx, index, c, shards, opt)
|
||
case "Max":
|
||
statFn()
|
||
return e.executeMax(ctx, qcx, index, c, shards, opt)
|
||
case "MinRow":
|
||
statFn()
|
||
return e.executeMinRow(ctx, qcx, index, c, shards, opt)
|
||
case "MaxRow":
|
||
statFn()
|
||
return e.executeMaxRow(ctx, qcx, index, c, shards, opt)
|
||
case "Clear":
|
||
statFn()
|
||
return e.executeClearBit(ctx, qcx, index, c, opt)
|
||
case "ClearRow":
|
||
statFn()
|
||
return e.executeClearRow(ctx, qcx, index, c, shards, opt)
|
||
case "Distinct":
|
||
statFn()
|
||
return e.executeDistinct(ctx, qcx, index, c, shards, opt)
|
||
case "Store":
|
||
statFn()
|
||
return e.executeSetRow(ctx, qcx, index, c, shards, opt)
|
||
case "Count":
|
||
statFn()
|
||
return e.executeCount(ctx, qcx, index, c, shards, opt)
|
||
case "Set":
|
||
statFn()
|
||
return e.executeSet(ctx, qcx, index, c, opt)
|
||
case "SetRowAttrs":
|
||
statFn()
|
||
return nil, e.executeSetRowAttrs(ctx, qcx, index, c, opt)
|
||
case "SetColumnAttrs":
|
||
statFn()
|
||
return nil, e.executeSetColumnAttrs(ctx, qcx, index, c, opt)
|
||
case "TopN":
|
||
statFn()
|
||
return e.executeTopN(ctx, qcx, index, c, shards, opt)
|
||
case "Rows":
|
||
statFn()
|
||
return e.executeRows(ctx, qcx, index, c, shards, opt)
|
||
case "Extract":
|
||
statFn()
|
||
return e.executeExtract(ctx, qcx, index, c, shards, opt)
|
||
case "GroupBy":
|
||
statFn()
|
||
return e.executeGroupBy(ctx, qcx, index, c, shards, opt)
|
||
case "Options":
|
||
statFn()
|
||
return e.executeOptionsCall(ctx, qcx, index, c, shards, opt)
|
||
case "IncludesColumn":
|
||
return e.executeIncludesColumnCall(ctx, qcx, index, c, shards, opt)
|
||
case "FieldValue":
|
||
statFn()
|
||
return e.executeFieldValueCall(ctx, qcx, index, c, shards, opt)
|
||
case "Precomputed":
|
||
return e.executePrecomputedCall(ctx, qcx, index, c, shards, opt)
|
||
default: // e.g. "Row", "Union", "Intersect" or anything that returns a bitmap.
|
||
statFn()
|
||
return e.executeBitmapCall(ctx, qcx, index, c, shards, opt)
|
||
}
|
||
}
|
||
|
||
// validateCallArgs ensures that the value types in call.Args are expected.
|
||
func (e *executor) validateCallArgs(c *pql.Call) error {
|
||
if _, ok := c.Args["ids"]; ok {
|
||
switch v := c.Args["ids"].(type) {
|
||
case []int64, []uint64:
|
||
// noop
|
||
case []interface{}:
|
||
b := make([]int64, len(v))
|
||
for i := range v {
|
||
b[i] = v[i].(int64)
|
||
}
|
||
c.Args["ids"] = b
|
||
default:
|
||
return fmt.Errorf("invalid call.Args[ids]: %s", v)
|
||
}
|
||
}
|
||
return nil
|
||
}
|
||
|
||
func (e *executor) executeOptionsCall(ctx context.Context, qcx *Qcx, index string, c *pql.Call, shards []uint64, opt *execOptions) (interface{}, error) {
|
||
span, ctx := tracing.StartSpanFromContext(ctx, "Executor.executeOptionsCall")
|
||
defer span.Finish()
|
||
|
||
optCopy := &execOptions{}
|
||
*optCopy = *opt
|
||
if arg, ok := c.Args["columnAttrs"]; ok {
|
||
if value, ok := arg.(bool); ok {
|
||
opt.ColumnAttrs = value
|
||
} else {
|
||
return nil, errors.New("Query(): columnAttrs must be a bool")
|
||
}
|
||
}
|
||
if arg, ok := c.Args["excludeRowAttrs"]; ok {
|
||
if value, ok := arg.(bool); ok {
|
||
optCopy.ExcludeRowAttrs = value
|
||
} else {
|
||
return nil, errors.New("Query(): excludeRowAttrs must be a bool")
|
||
}
|
||
}
|
||
if arg, ok := c.Args["excludeColumns"]; ok {
|
||
if value, ok := arg.(bool); ok {
|
||
optCopy.ExcludeColumns = value
|
||
} else {
|
||
return nil, errors.New("Query(): excludeColumns must be a bool")
|
||
}
|
||
}
|
||
if arg, ok := c.Args["shards"]; ok {
|
||
if optShards, ok := arg.([]interface{}); ok {
|
||
shards = []uint64{}
|
||
for _, s := range optShards {
|
||
if shard, ok := s.(int64); ok {
|
||
shards = append(shards, uint64(shard))
|
||
} else {
|
||
return nil, errors.New("Query(): shards must be a list of unsigned integers")
|
||
}
|
||
|
||
}
|
||
} else {
|
||
return nil, errors.New("Query(): shards must be a list of unsigned integers")
|
||
}
|
||
}
|
||
return e.executeCall(ctx, qcx, index, c.Children[0], shards, optCopy)
|
||
}
|
||
|
||
// executeIncludesColumnCall executes an IncludesColumn() call.
|
||
func (e *executor) executeIncludesColumnCall(ctx context.Context, qcx *Qcx, index string, c *pql.Call, shards []uint64, opt *execOptions) (bool, error) {
|
||
// Get the shard containing the column, since that's the only
|
||
// shard that needs to execute this query.
|
||
var shard uint64
|
||
col, ok, err := c.UintArg("column")
|
||
if err != nil {
|
||
return false, errors.Wrap(err, "getting column from args")
|
||
} else if !ok {
|
||
return false, errors.New("IncludesColumn call must specify a column")
|
||
}
|
||
shard = col / ShardWidth
|
||
|
||
// If shard is not in shards, bail early.
|
||
if !uint64InSlice(shard, shards) {
|
||
return false, nil
|
||
}
|
||
|
||
// Execute calls in bulk on each remote node and merge.
|
||
mapFn := func(ctx context.Context, shard uint64) (_ interface{}, err error) {
|
||
return e.executeIncludesColumnCallShard(ctx, qcx, index, c, shard, col)
|
||
}
|
||
|
||
// Merge returned results at coordinating node.
|
||
reduceFn := func(ctx context.Context, prev, v interface{}) interface{} {
|
||
other, _ := prev.(bool)
|
||
return other || v.(bool)
|
||
}
|
||
|
||
result, err := e.mapReduce(ctx, index, []uint64{shard}, c, opt, mapFn, reduceFn)
|
||
if err != nil {
|
||
return false, err
|
||
}
|
||
return result.(bool), nil
|
||
}
|
||
|
||
// executeFieldValueCall executes a FieldValue() call.
|
||
func (e *executor) executeFieldValueCall(ctx context.Context, qcx *Qcx, index string, c *pql.Call, shards []uint64, opt *execOptions) (_ ValCount, err error) {
|
||
fieldName, ok := c.Args["field"].(string)
|
||
if !ok || fieldName == "" {
|
||
return ValCount{}, ErrFieldRequired
|
||
}
|
||
|
||
colKey, ok := c.Args["column"]
|
||
if !ok || colKey == "" {
|
||
return ValCount{}, ErrColumnRequired
|
||
}
|
||
|
||
// Fetch index.
|
||
idx := e.Holder.Index(index)
|
||
if idx == nil {
|
||
return ValCount{}, newNotFoundError(ErrIndexNotFound, index)
|
||
}
|
||
|
||
// Fetch field.
|
||
field := idx.Field(fieldName)
|
||
if field == nil {
|
||
return ValCount{}, newNotFoundError(ErrFieldNotFound, fieldName)
|
||
}
|
||
|
||
var colID uint64
|
||
if key, ok := colKey.(string); ok && idx.Keys() {
|
||
id, err := e.Cluster.translateIndexKey(ctx, index, key, false)
|
||
if err != nil {
|
||
return ValCount{}, errors.Wrap(err, "getting column id")
|
||
}
|
||
colID = id
|
||
} else {
|
||
id, ok, err := c.UintArg("column")
|
||
if !ok || err != nil {
|
||
return ValCount{}, errors.Wrap(err, "getting column argument")
|
||
}
|
||
colID = id
|
||
}
|
||
|
||
shard := colID / ShardWidth
|
||
|
||
// Execute calls in bulk on each remote node and merge.
|
||
mapFn := func(ctx context.Context, shard uint64) (_ interface{}, err error) {
|
||
return e.executeFieldValueCallShard(ctx, qcx, field, colID, shard)
|
||
}
|
||
|
||
// Select single returned result at coordinating node.
|
||
reduceFn := func(ctx context.Context, prev, v interface{}) interface{} {
|
||
other, _ := prev.(ValCount)
|
||
if other.Count == 1 {
|
||
return other
|
||
}
|
||
return v
|
||
}
|
||
|
||
result, err := e.mapReduce(ctx, index, []uint64{shard}, c, opt, mapFn, reduceFn)
|
||
if err != nil {
|
||
return ValCount{}, errors.Wrap(err, "map reduce")
|
||
}
|
||
other, _ := result.(ValCount)
|
||
|
||
return other, nil
|
||
}
|
||
|
||
func (e *executor) executeFieldValueCallShard(ctx context.Context, qcx *Qcx, field *Field, col uint64, shard uint64) (_ ValCount, err error) {
|
||
|
||
idx := e.Holder.Index(field.index)
|
||
tx, finisher := qcx.GetTx(Txo{Write: !writable, Index: idx, Shard: shard})
|
||
defer finisher(&err)
|
||
|
||
value, exists, err := field.Value(tx, col)
|
||
if err != nil {
|
||
return ValCount{}, errors.Wrap(err, "getting field value")
|
||
} else if !exists {
|
||
return ValCount{}, nil
|
||
}
|
||
|
||
other := ValCount{
|
||
Count: 1,
|
||
}
|
||
|
||
if field.Type() == FieldTypeInt {
|
||
other.Val = value
|
||
} else if field.Type() == FieldTypeDecimal {
|
||
other.DecimalVal = &pql.Decimal{
|
||
Value: value,
|
||
Scale: field.Options().Scale}
|
||
other.FloatVal = 0
|
||
other.Val = 0
|
||
}
|
||
|
||
return other, nil
|
||
}
|
||
|
||
// executeLimitCall executes a Limit() call, **rewriting it to a precomputed call**.
|
||
func (e *executor) executeLimitCall(ctx context.Context, qcx *Qcx, index string, c *pql.Call, shards []uint64, opt *execOptions) error {
|
||
bitmapCall := c.Children[0]
|
||
|
||
limit, hasLimit, err := c.UintArg("limit")
|
||
if err != nil {
|
||
return errors.Wrap(err, "getting limit")
|
||
}
|
||
offset, _, err := c.UintArg("offset")
|
||
if err != nil {
|
||
return errors.Wrap(err, "getting offset")
|
||
}
|
||
|
||
if !hasLimit {
|
||
limit = math.MaxUint64
|
||
}
|
||
|
||
// skip tracks the number of records left to be skipped
|
||
// in support of getting to the offset.
|
||
var skip uint64 = offset
|
||
|
||
// got tracks the number of records gotten to that point.
|
||
var got uint64
|
||
|
||
c.Precomputed = make(map[uint64]interface{})
|
||
|
||
for _, shard := range shards {
|
||
// Execute calls in bulk on each remote node and merge.
|
||
mapFn := func(ctx context.Context, shard uint64) (_ interface{}, err error) {
|
||
return e.executeBitmapCallShard(ctx, qcx, index, bitmapCall, shard)
|
||
}
|
||
|
||
// Merge returned results at coordinating node.
|
||
reduceFn := func(ctx context.Context, prev, v interface{}) interface{} {
|
||
if err := ctx.Err(); err != nil {
|
||
return err
|
||
}
|
||
other, _ := prev.(*Row)
|
||
if other == nil {
|
||
other = NewRow()
|
||
}
|
||
other.Merge(v.(*Row))
|
||
return other
|
||
}
|
||
|
||
result, err := e.mapReduce(ctx, index, []uint64{shard}, c, opt, mapFn, reduceFn)
|
||
if err != nil {
|
||
return errors.Wrap(err, "limit map reduce")
|
||
}
|
||
|
||
row, _ := result.(*Row)
|
||
|
||
segCnt := row.Count()
|
||
|
||
// If this segment doesn't reach the offset, skip it.
|
||
if segCnt <= skip {
|
||
skip -= segCnt
|
||
continue
|
||
}
|
||
|
||
// This segment doesn't have enough to finish fulfilling the limit
|
||
// (or it has exactly enough).
|
||
if segCnt-skip <= limit-got {
|
||
if skip == 0 {
|
||
c.Precomputed[shard] = row
|
||
} else {
|
||
cols := row.Columns()
|
||
partialRow := NewRow()
|
||
for _, bit := range cols[skip:] {
|
||
partialRow.SetBit(bit)
|
||
}
|
||
c.Precomputed[shard] = partialRow
|
||
}
|
||
got += segCnt - skip
|
||
// In the case where this segment exactly fulfills the limit, break.
|
||
if got == limit {
|
||
break
|
||
}
|
||
skip = 0
|
||
continue
|
||
}
|
||
|
||
// This segment has more records than the remaining limit requires.
|
||
cols := row.Columns()
|
||
partialRow := NewRow()
|
||
for _, bit := range cols[skip : skip+limit-got] {
|
||
partialRow.SetBit(bit)
|
||
}
|
||
c.Precomputed[shard] = partialRow
|
||
break
|
||
}
|
||
|
||
c.Name = "Precomputed"
|
||
return nil
|
||
}
|
||
|
||
// executeIncludesColumnCallShard
|
||
func (e *executor) executeIncludesColumnCallShard(ctx context.Context, qcx *Qcx, index string, c *pql.Call, shard uint64, column uint64) (_ bool, err error) {
|
||
|
||
span, ctx := tracing.StartSpanFromContext(ctx, "Executor.executeIncludesColumnCallShard")
|
||
defer span.Finish()
|
||
|
||
if len(c.Children) == 1 {
|
||
row, err := e.executeBitmapCallShard(ctx, qcx, index, c.Children[0], shard)
|
||
if err != nil {
|
||
return false, errors.Wrap(err, "executing bitmap call")
|
||
}
|
||
return row.Includes(column), nil
|
||
}
|
||
|
||
return false, errors.New("IncludesColumn call must specify a row query")
|
||
}
|
||
|
||
// executeSum executes a Sum() call.
|
||
func (e *executor) executeSum(ctx context.Context, qcx *Qcx, index string, c *pql.Call, shards []uint64, opt *execOptions) (_ ValCount, err error) {
|
||
span, ctx := tracing.StartSpanFromContext(ctx, "Executor.executeSum")
|
||
defer span.Finish()
|
||
|
||
fieldName, ok := c.Args["field"].(string)
|
||
if !ok || fieldName == "" {
|
||
return ValCount{}, errors.New("Sum(): field required")
|
||
}
|
||
|
||
if len(c.Children) > 1 {
|
||
return ValCount{}, errors.New("Sum() only accepts a single bitmap input")
|
||
}
|
||
|
||
// Execute calls in bulk on each remote node and merge.
|
||
mapFn := func(ctx context.Context, shard uint64) (_ interface{}, err error) {
|
||
return e.executeSumCountShard(ctx, qcx, index, c, nil, shard)
|
||
}
|
||
|
||
// Merge returned results at coordinating node.
|
||
reduceFn := func(ctx context.Context, prev, v interface{}) interface{} {
|
||
other, _ := prev.(ValCount)
|
||
return other.add(v.(ValCount))
|
||
}
|
||
|
||
result, err := e.mapReduce(ctx, index, shards, c, opt, mapFn, reduceFn)
|
||
if err != nil {
|
||
return ValCount{}, err
|
||
}
|
||
other, _ := result.(ValCount)
|
||
|
||
if other.Count == 0 {
|
||
return ValCount{}, nil
|
||
}
|
||
|
||
// scale summed response if it's a decimal field and this is
|
||
// not a remote query (we're about to return to original client).
|
||
if !opt.Remote {
|
||
field := e.Holder.Field(index, fieldName)
|
||
if field == nil {
|
||
return ValCount{}, newNotFoundError(ErrFieldNotFound, fieldName)
|
||
}
|
||
if field.Type() == FieldTypeDecimal {
|
||
other.DecimalVal = &pql.Decimal{
|
||
Value: other.Val,
|
||
Scale: field.Options().Scale}
|
||
other.FloatVal = 0
|
||
other.Val = 0
|
||
}
|
||
}
|
||
|
||
return other, nil
|
||
}
|
||
|
||
// executeDistinct executes a Distinct call on a field.
|
||
func (e *executor) executeDistinct(ctx context.Context, qcx *Qcx, index string, c *pql.Call, shards []uint64, opt *execOptions) (SignedRow, error) {
|
||
span, ctx := tracing.StartSpanFromContext(ctx, "Executor.executeDistinct")
|
||
defer span.Finish()
|
||
|
||
field := c.Args["field"]
|
||
if field == "" {
|
||
return SignedRow{}, fmt.Errorf("plugin operation %s(): field required", c.Name)
|
||
}
|
||
|
||
// Execute calls in bulk on each remote node and merge.
|
||
mapFn := func(ctx context.Context, shard uint64) (_ interface{}, err error) {
|
||
return e.executeDistinctShard(ctx, qcx, index, c, shard)
|
||
}
|
||
|
||
// Merge returned results at coordinating node.
|
||
reduceFn := func(ctx context.Context, prev, v interface{}) interface{} {
|
||
other, _ := prev.(SignedRow)
|
||
if err := ctx.Err(); err != nil {
|
||
return err
|
||
}
|
||
return other.union(v.(SignedRow))
|
||
}
|
||
|
||
result, err := e.mapReduce(ctx, index, shards, c, opt, mapFn, reduceFn)
|
||
if err != nil {
|
||
return SignedRow{}, err
|
||
}
|
||
other, _ := result.(SignedRow)
|
||
other.field = field.(string)
|
||
|
||
return other, nil
|
||
}
|
||
|
||
// executeMin executes a Min() call.
|
||
func (e *executor) executeMin(ctx context.Context, qcx *Qcx, index string, c *pql.Call, shards []uint64, opt *execOptions) (_ ValCount, err error) {
|
||
span, ctx := tracing.StartSpanFromContext(ctx, "Executor.executeMin")
|
||
defer span.Finish()
|
||
if field := c.Args["field"]; field == "" {
|
||
return ValCount{}, errors.New("Min(): field required")
|
||
}
|
||
|
||
if len(c.Children) > 1 {
|
||
return ValCount{}, errors.New("Min() only accepts a single bitmap input")
|
||
}
|
||
|
||
// Execute calls in bulk on each remote node and merge.
|
||
mapFn := func(ctx context.Context, shard uint64) (_ interface{}, err error) {
|
||
return e.executeMinShard(ctx, qcx, index, c, shard)
|
||
}
|
||
|
||
// Merge returned results at coordinating node.
|
||
reduceFn := func(ctx context.Context, prev, v interface{}) interface{} {
|
||
other, _ := prev.(ValCount)
|
||
return other.smaller(v.(ValCount))
|
||
}
|
||
|
||
result, err := e.mapReduce(ctx, index, shards, c, opt, mapFn, reduceFn)
|
||
if err != nil {
|
||
return ValCount{}, err
|
||
}
|
||
other, _ := result.(ValCount)
|
||
|
||
if other.Count == 0 {
|
||
return ValCount{}, nil
|
||
}
|
||
return other, nil
|
||
}
|
||
|
||
// executeMax executes a Max() call.
|
||
func (e *executor) executeMax(ctx context.Context, qcx *Qcx, index string, c *pql.Call, shards []uint64, opt *execOptions) (_ ValCount, err error) {
|
||
span, ctx := tracing.StartSpanFromContext(ctx, "Executor.executeMax")
|
||
defer span.Finish()
|
||
|
||
if field := c.Args["field"]; field == "" {
|
||
return ValCount{}, errors.New("Max(): field required")
|
||
}
|
||
|
||
if len(c.Children) > 1 {
|
||
return ValCount{}, errors.New("Max() only accepts a single bitmap input")
|
||
}
|
||
|
||
// Execute calls in bulk on each remote node and merge.
|
||
mapFn := func(ctx context.Context, shard uint64) (_ interface{}, err error) {
|
||
return e.executeMaxShard(ctx, qcx, index, c, shard)
|
||
}
|
||
|
||
// Merge returned results at coordinating node.
|
||
reduceFn := func(ctx context.Context, prev, v interface{}) interface{} {
|
||
other, _ := prev.(ValCount)
|
||
return other.larger(v.(ValCount))
|
||
}
|
||
|
||
result, err := e.mapReduce(ctx, index, shards, c, opt, mapFn, reduceFn)
|
||
if err != nil {
|
||
return ValCount{}, err
|
||
}
|
||
other, _ := result.(ValCount)
|
||
|
||
if other.Count == 0 {
|
||
return ValCount{}, nil
|
||
}
|
||
return other, nil
|
||
}
|
||
|
||
// executeMinRow executes a MinRow() call.
|
||
func (e *executor) executeMinRow(ctx context.Context, qcx *Qcx, index string, c *pql.Call, shards []uint64, opt *execOptions) (_ interface{}, err error) {
|
||
span, ctx := tracing.StartSpanFromContext(ctx, "Executor.executeMinRow")
|
||
defer span.Finish()
|
||
|
||
if field := c.Args["field"]; field == "" {
|
||
return ValCount{}, errors.New("MinRow(): field required")
|
||
}
|
||
|
||
// Execute calls in bulk on each remote node and merge.
|
||
mapFn := func(ctx context.Context, shard uint64) (_ interface{}, err error) {
|
||
return e.executeMinRowShard(ctx, qcx, index, c, shard)
|
||
}
|
||
|
||
// Merge returned results at coordinating node.
|
||
reduceFn := func(ctx context.Context, prev, v interface{}) interface{} {
|
||
// if minRowID exists, and if it is smaller than the other one return it.
|
||
// otherwise return the minRowID of the one which exists.
|
||
if prev == nil {
|
||
return v
|
||
} else if v == nil {
|
||
return prev
|
||
}
|
||
prevp, _ := prev.(PairField)
|
||
vp, _ := v.(PairField)
|
||
if prevp.Pair.Count > 0 && vp.Pair.Count > 0 {
|
||
if prevp.Pair.ID < vp.Pair.ID {
|
||
return prevp
|
||
}
|
||
return vp
|
||
} else if prevp.Pair.Count > 0 {
|
||
return prevp
|
||
}
|
||
return vp
|
||
}
|
||
|
||
return e.mapReduce(ctx, index, shards, c, opt, mapFn, reduceFn)
|
||
}
|
||
|
||
// executeMaxRow executes a MaxRow() call.
|
||
func (e *executor) executeMaxRow(ctx context.Context, qcx *Qcx, index string, c *pql.Call, shards []uint64, opt *execOptions) (_ interface{}, err error) {
|
||
span, ctx := tracing.StartSpanFromContext(ctx, "Executor.executeMaxRow")
|
||
defer span.Finish()
|
||
|
||
if field := c.Args["field"]; field == "" {
|
||
return ValCount{}, errors.New("MaxRow(): field required")
|
||
}
|
||
|
||
// Execute calls in bulk on each remote node and merge.
|
||
mapFn := func(ctx context.Context, shard uint64) (_ interface{}, err error) {
|
||
return e.executeMaxRowShard(ctx, qcx, index, c, shard)
|
||
}
|
||
|
||
// Merge returned results at coordinating node.
|
||
reduceFn := func(ctx context.Context, prev, v interface{}) interface{} {
|
||
// if minRowID exists, and if it is smaller than the other one return it.
|
||
// otherwise return the minRowID of the one which exists.
|
||
if prev == nil {
|
||
return v
|
||
} else if v == nil {
|
||
return prev
|
||
}
|
||
prevp, _ := prev.(PairField)
|
||
vp, _ := v.(PairField)
|
||
if prevp.Pair.Count > 0 && vp.Pair.Count > 0 {
|
||
if prevp.Pair.ID > vp.Pair.ID {
|
||
return prevp
|
||
}
|
||
return vp
|
||
} else if prevp.Pair.Count > 0 {
|
||
return prevp
|
||
}
|
||
return vp
|
||
}
|
||
|
||
return e.mapReduce(ctx, index, shards, c, opt, mapFn, reduceFn)
|
||
}
|
||
|
||
// executePrecomputedCall pretends to execute a call that we have a precomputed value for.
|
||
func (e *executor) executePrecomputedCall(ctx context.Context, qcx *Qcx, index string, c *pql.Call, shards []uint64, opt *execOptions) (_ *Row, err error) {
|
||
span, _ := tracing.StartSpanFromContext(ctx, "Executor.executePrecomputedCall")
|
||
defer span.Finish()
|
||
result := NewRow()
|
||
|
||
for _, row := range c.Precomputed {
|
||
result.Merge(row.(*Row))
|
||
}
|
||
return result, nil
|
||
}
|
||
|
||
// executeBitmapCall executes a call that returns a bitmap.
|
||
func (e *executor) executeBitmapCall(ctx context.Context, qcx *Qcx, index string, c *pql.Call, shards []uint64, opt *execOptions) (_ *Row, err error) {
|
||
|
||
span, ctx := tracing.StartSpanFromContext(ctx, "Executor.executeBitmapCall")
|
||
span.LogKV("pqlCallName", c.Name)
|
||
defer span.Finish()
|
||
|
||
indexTag := "index:" + index
|
||
metricName := "query_" + strings.ToLower(c.Name) + "_total"
|
||
if c.Name == "Row" && c.HasConditionArg() {
|
||
metricName = "query_row_bsi_total"
|
||
}
|
||
if !opt.Remote {
|
||
e.Holder.Stats.CountWithCustomTags(metricName, 1, 1.0, []string{indexTag})
|
||
}
|
||
|
||
// Execute calls in bulk on each remote node and merge.
|
||
mapFn := func(ctx context.Context, shard uint64) (_ interface{}, err error) {
|
||
return e.executeBitmapCallShard(ctx, qcx, index, c, shard)
|
||
}
|
||
|
||
// Merge returned results at coordinating node.
|
||
reduceFn := func(ctx context.Context, prev, v interface{}) interface{} {
|
||
other, _ := prev.(*Row)
|
||
if other == nil {
|
||
|
||
other = NewRow() // bug! this row ends up containing Badger Txn data that should be accessed outside the Txn.
|
||
}
|
||
if err := ctx.Err(); err != nil {
|
||
return err
|
||
}
|
||
other.Merge(v.(*Row))
|
||
return other
|
||
}
|
||
|
||
other, err := e.mapReduce(ctx, index, shards, c, opt, mapFn, reduceFn)
|
||
if err != nil {
|
||
return nil, errors.Wrap(err, "map reduce")
|
||
}
|
||
|
||
// Attach attributes for non-BSI Row() calls.
|
||
// If the column label is used then return column attributes.
|
||
// If the row label is used then return bitmap attributes.
|
||
row, _ := other.(*Row)
|
||
if c.Name == "Row" && !c.HasConditionArg() {
|
||
if opt.ExcludeRowAttrs {
|
||
row.Attrs = map[string]interface{}{}
|
||
} else {
|
||
idx := e.Holder.Index(index)
|
||
if idx != nil {
|
||
if columnID, ok, err := c.UintArg("_" + columnLabel); ok && err == nil {
|
||
attrs, err := idx.ColumnAttrStore().Attrs(columnID)
|
||
if err != nil {
|
||
return nil, errors.Wrap(err, "getting column attrs")
|
||
}
|
||
row.Attrs = attrs
|
||
} else if err != nil {
|
||
return nil, err
|
||
} else {
|
||
// field, _ := c.Args["field"].(string)
|
||
fieldName, _ := c.FieldArg()
|
||
if fr := idx.Field(fieldName); fr != nil {
|
||
rowID, _, err := c.UintArg(fieldName)
|
||
if err != nil {
|
||
return nil, errors.Wrap(err, "getting row")
|
||
}
|
||
attrs, err := fr.RowAttrStore().Attrs(rowID)
|
||
if err != nil {
|
||
return nil, errors.Wrap(err, "getting row attrs")
|
||
}
|
||
row.Attrs = attrs
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
if opt.ExcludeColumns {
|
||
row.segments = []rowSegment{}
|
||
}
|
||
|
||
return row, nil
|
||
}
|
||
|
||
// executeBitmapCallShard executes a bitmap call for a single shard.
|
||
func (e *executor) executeBitmapCallShard(ctx context.Context, qcx *Qcx, index string, c *pql.Call, shard uint64) (_ *Row, err error) {
|
||
if err := validateQueryContext(ctx); err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
span, ctx := tracing.StartSpanFromContext(ctx, "Executor.executeBitmapCallShard")
|
||
defer span.Finish()
|
||
|
||
switch c.Name {
|
||
case "Row", "Range":
|
||
return e.executeRowShard(ctx, qcx, index, c, shard)
|
||
case "Difference":
|
||
return e.executeDifferenceShard(ctx, qcx, index, c, shard)
|
||
case "Intersect":
|
||
return e.executeIntersectShard(ctx, qcx, index, c, shard)
|
||
case "Union":
|
||
return e.executeUnionShard(ctx, qcx, index, c, shard)
|
||
case "Xor":
|
||
return e.executeXorShard(ctx, qcx, index, c, shard)
|
||
case "Not":
|
||
return e.executeNotShard(ctx, qcx, index, c, shard)
|
||
case "Shift":
|
||
return e.executeShiftShard(ctx, qcx, index, c, shard)
|
||
case "All": // Allow a shard computation to use All()
|
||
return e.executeAllCallShard(ctx, qcx, index, c, shard)
|
||
case "Distinct":
|
||
return nil, errors.New("Distinct shouldn't be hit as a bitmap call")
|
||
case "Precomputed":
|
||
return e.executePrecomputedCallShard(ctx, qcx, index, c, shard)
|
||
default:
|
||
return nil, fmt.Errorf("unknown call: %s", c.Name)
|
||
}
|
||
}
|
||
|
||
// executeDistinctShard executes a Distinct call on a single shard, yielding
|
||
// a SignedRow of the values found.
|
||
func (e *executor) executeDistinctShard(ctx context.Context, qcx *Qcx, index string, c *pql.Call, shard uint64) (result SignedRow, err error) {
|
||
span, ctx := tracing.StartSpanFromContext(ctx, "Executor.executeDistinctShard")
|
||
defer span.Finish()
|
||
|
||
idx := e.Holder.Index(index)
|
||
|
||
var filter *Row
|
||
var filterBitmap *roaring.Bitmap
|
||
if len(c.Children) == 1 {
|
||
row, err := e.executeBitmapCallShard(ctx, qcx, index, c.Children[0], shard)
|
||
if err != nil {
|
||
return result, errors.Wrap(err, "executing bitmap call")
|
||
}
|
||
filter = row
|
||
if filter != nil && len(filter.segments) > 0 {
|
||
filterBitmap = filter.segments[0].data
|
||
} else {
|
||
filterBitmap = roaring.NewFileBitmap()
|
||
}
|
||
}
|
||
|
||
fieldName, _ := c.Args["field"].(string)
|
||
|
||
field := e.Holder.Field(index, fieldName)
|
||
if field == nil {
|
||
return result, nil
|
||
}
|
||
|
||
bsig := field.bsiGroup(fieldName)
|
||
if bsig == nil {
|
||
return result, nil
|
||
}
|
||
view := viewBSIGroupPrefix + fieldName
|
||
|
||
depth := uint64(bsig.BitDepth)
|
||
offset := bsig.Base
|
||
|
||
tx, finisher := qcx.GetTx(Txo{Write: !writable, Index: idx, Shard: shard})
|
||
defer finisher(&err)
|
||
|
||
existsBitmap, err := tx.OffsetRange(index, fieldName, view, shard, 0, ShardWidth*0, ShardWidth*1)
|
||
if err != nil {
|
||
return result, err
|
||
}
|
||
if filter != nil {
|
||
existsBitmap = existsBitmap.Intersect(filterBitmap)
|
||
}
|
||
if !existsBitmap.Any() {
|
||
return result, nil
|
||
}
|
||
|
||
signBitmap, err := tx.OffsetRange(index, fieldName, view, shard, 0, ShardWidth*1, ShardWidth*2)
|
||
if err != nil {
|
||
return result, nil
|
||
}
|
||
|
||
dataBitmaps := make([]*roaring.Bitmap, depth)
|
||
|
||
for i := uint64(0); i < depth; i++ {
|
||
dataBitmaps[i], err = tx.OffsetRange(index, fieldName, view, shard, 0, ShardWidth*(i+2), ShardWidth*(i+3))
|
||
if err != nil {
|
||
return result, err
|
||
}
|
||
}
|
||
|
||
// we need spaces for sign bit, existence/filter bit, and data
|
||
// row bits, which we'll be grabbing 64K bits at a time
|
||
stashWords := make([]uint64, 1024*(depth+2))
|
||
bitStashes := make([][]uint64, depth)
|
||
for i := uint64(0); i < depth; i++ {
|
||
start := i * 1024
|
||
last := start + 1024
|
||
bitStashes[i] = stashWords[start:last]
|
||
i++
|
||
}
|
||
stashOffset := depth * 1024
|
||
existStash := stashWords[stashOffset : stashOffset+1024]
|
||
signStash := stashWords[stashOffset+1024 : stashOffset+2048]
|
||
dataBits := make([][]uint64, depth)
|
||
|
||
posValues := make([]uint64, 0, 64)
|
||
negValues := make([]uint64, 0, 64)
|
||
|
||
posBitmap := roaring.NewFileBitmap()
|
||
negBitmap := roaring.NewFileBitmap()
|
||
|
||
existIterator, _ := existsBitmap.Containers.Iterator(0)
|
||
for existIterator.Next() {
|
||
key, value := existIterator.Value()
|
||
if value.N() == 0 {
|
||
continue
|
||
}
|
||
exists := value.AsBitmap(existStash)
|
||
sign := signBitmap.Containers.Get(key).AsBitmap(signStash)
|
||
for i := uint64(0); i < depth; i++ {
|
||
dataBits[i] = dataBitmaps[i].Containers.Get(key).AsBitmap(bitStashes[i])
|
||
}
|
||
for idx, word := range exists {
|
||
// mask holds a mask we can test the other words against.
|
||
mask := uint64(1)
|
||
for word != 0 {
|
||
shift := uint(bits.TrailingZeros64(word))
|
||
// we shift one *more* than that, to move the
|
||
// actual one bit off.
|
||
word >>= shift + 1
|
||
mask <<= shift
|
||
value := int64(0)
|
||
for b := uint64(0); b < depth; b++ {
|
||
if dataBits[b][idx]&mask != 0 {
|
||
value += (1 << b)
|
||
}
|
||
}
|
||
if sign[idx]&mask != 0 {
|
||
value *= -1
|
||
}
|
||
value += int64(offset)
|
||
if value < 0 {
|
||
negValues = append(negValues, uint64(-value))
|
||
} else {
|
||
posValues = append(posValues, uint64(value))
|
||
}
|
||
// and now we processed that bit, so we move the mask over one.
|
||
mask <<= 1
|
||
}
|
||
if len(negValues) > 0 {
|
||
_, _ = negBitmap.AddN(negValues...)
|
||
negValues = negValues[:0]
|
||
}
|
||
if len(posValues) > 0 {
|
||
_, _ = posBitmap.AddN(posValues...)
|
||
posValues = posValues[:0]
|
||
}
|
||
}
|
||
}
|
||
return SignedRow{
|
||
Neg: NewRowFromBitmap(negBitmap),
|
||
Pos: NewRowFromBitmap(posBitmap),
|
||
}, nil
|
||
}
|
||
|
||
// executeSumCountShard calculates the sum and count for bsiGroups on a shard.
|
||
func (e *executor) executeSumCountShard(ctx context.Context, qcx *Qcx, index string, c *pql.Call, filter *Row, shard uint64) (_ ValCount, err error) {
|
||
span, ctx := tracing.StartSpanFromContext(ctx, "Executor.executeSumCountShard")
|
||
defer span.Finish()
|
||
|
||
// use tx to keep consistency between
|
||
// the filter and the later count.
|
||
idx := e.Holder.Index(index)
|
||
|
||
// Only calculate the filter if it doesn't exist and a child call as been passed in.
|
||
if filter == nil && len(c.Children) == 1 {
|
||
|
||
row, err := e.executeBitmapCallShard(ctx, qcx, index, c.Children[0], shard)
|
||
if err != nil {
|
||
return ValCount{}, errors.Wrap(err, "executing bitmap call")
|
||
}
|
||
filter = row
|
||
}
|
||
|
||
fieldName, _ := c.Args["field"].(string)
|
||
|
||
field := e.Holder.Field(index, fieldName)
|
||
if field == nil {
|
||
return ValCount{}, nil
|
||
}
|
||
|
||
bsig := field.bsiGroup(fieldName)
|
||
if bsig == nil {
|
||
return ValCount{}, nil
|
||
}
|
||
|
||
fragment := e.Holder.fragment(index, fieldName, viewBSIGroupPrefix+fieldName, shard)
|
||
if fragment == nil {
|
||
return ValCount{}, nil
|
||
}
|
||
|
||
tx, finisher := qcx.GetTx(Txo{Write: !writable, Index: idx, Fragment: fragment, Shard: shard})
|
||
defer finisher(&err)
|
||
|
||
sumspan, _ := tracing.StartSpanFromContext(ctx, "Executor.executeSumCountShard_fragment.sum")
|
||
defer sumspan.Finish()
|
||
vsum, vcount, err := fragment.sum(tx, filter, bsig.BitDepth)
|
||
if err != nil {
|
||
return ValCount{}, errors.Wrap(err, "computing sum")
|
||
}
|
||
return ValCount{
|
||
Val: int64(vsum) + (int64(vcount) * bsig.Base),
|
||
Count: int64(vcount),
|
||
}, nil
|
||
}
|
||
|
||
// executeMinShard calculates the min for bsiGroups on a shard.
|
||
func (e *executor) executeMinShard(ctx context.Context, qcx *Qcx, index string, c *pql.Call, shard uint64) (_ ValCount, err error) {
|
||
span, ctx := tracing.StartSpanFromContext(ctx, "Executor.executeMinShard")
|
||
defer span.Finish()
|
||
|
||
idx := e.Holder.Index(index)
|
||
|
||
var filter *Row
|
||
if len(c.Children) == 1 {
|
||
row, err := e.executeBitmapCallShard(ctx, qcx, index, c.Children[0], shard)
|
||
if err != nil {
|
||
return ValCount{}, err
|
||
}
|
||
filter = row
|
||
}
|
||
|
||
fieldName, _ := c.Args["field"].(string)
|
||
|
||
field := e.Holder.Field(index, fieldName)
|
||
if field == nil {
|
||
return ValCount{}, nil
|
||
}
|
||
|
||
tx, finisher := qcx.GetTx(Txo{Write: !writable, Index: idx, Shard: shard})
|
||
defer finisher(&err)
|
||
return field.MinForShard(tx, shard, filter)
|
||
}
|
||
|
||
// executeMaxShard calculates the max for bsiGroups on a shard.
|
||
func (e *executor) executeMaxShard(ctx context.Context, qcx *Qcx, index string, c *pql.Call, shard uint64) (_ ValCount, err error) {
|
||
|
||
idx := e.Holder.Index(index)
|
||
|
||
var filter *Row
|
||
if len(c.Children) == 1 {
|
||
row, err := e.executeBitmapCallShard(ctx, qcx, index, c.Children[0], shard)
|
||
if err != nil {
|
||
return ValCount{}, err
|
||
}
|
||
filter = row
|
||
}
|
||
|
||
fieldName, _ := c.Args["field"].(string)
|
||
|
||
field := e.Holder.Field(index, fieldName)
|
||
if field == nil {
|
||
return ValCount{}, nil
|
||
}
|
||
|
||
tx, finisher := qcx.GetTx(Txo{Write: !writable, Index: idx, Shard: shard})
|
||
defer finisher(&err)
|
||
return field.MaxForShard(tx, shard, filter)
|
||
}
|
||
|
||
// executeMinRowShard returns the minimum row ID for a shard.
|
||
func (e *executor) executeMinRowShard(ctx context.Context, qcx *Qcx, index string, c *pql.Call, shard uint64) (_ PairField, err error) {
|
||
var filter *Row
|
||
|
||
if len(c.Children) == 1 {
|
||
row, err := e.executeBitmapCallShard(ctx, qcx, index, c.Children[0], shard)
|
||
if err != nil {
|
||
return PairField{}, err
|
||
}
|
||
filter = row
|
||
}
|
||
|
||
fieldName, _ := c.Args["field"].(string)
|
||
field := e.Holder.Field(index, fieldName)
|
||
if field == nil {
|
||
return PairField{}, nil
|
||
}
|
||
|
||
fragment := e.Holder.fragment(index, fieldName, viewStandard, shard)
|
||
if fragment == nil {
|
||
return PairField{}, nil
|
||
}
|
||
|
||
idx := e.Holder.Index(index)
|
||
tx, finisher := qcx.GetTx(Txo{Write: !writable, Index: idx, Fragment: fragment, Shard: fragment.shard})
|
||
defer finisher(&err)
|
||
|
||
minRowID, count, err := fragment.minRow(tx, filter)
|
||
if err != nil {
|
||
return PairField{}, err
|
||
}
|
||
|
||
return PairField{
|
||
Pair: Pair{
|
||
ID: minRowID,
|
||
Count: count,
|
||
},
|
||
Field: fieldName,
|
||
}, nil
|
||
}
|
||
|
||
// executeMaxRowShard returns the maximum row ID for a shard.
|
||
func (e *executor) executeMaxRowShard(ctx context.Context, qcx *Qcx, index string, c *pql.Call, shard uint64) (_ PairField, err error) {
|
||
|
||
var filter *Row
|
||
if len(c.Children) == 1 {
|
||
row, err := e.executeBitmapCallShard(ctx, qcx, index, c.Children[0], shard)
|
||
if err != nil {
|
||
return PairField{}, err
|
||
}
|
||
filter = row
|
||
}
|
||
|
||
fieldName, _ := c.Args["field"].(string)
|
||
field := e.Holder.Field(index, fieldName)
|
||
if field == nil {
|
||
return PairField{}, nil
|
||
}
|
||
|
||
fragment := e.Holder.fragment(index, fieldName, viewStandard, shard)
|
||
if fragment == nil {
|
||
return PairField{}, nil
|
||
}
|
||
|
||
idx := e.Holder.Index(index)
|
||
tx, finisher := qcx.GetTx(Txo{Write: !writable, Index: idx, Shard: shard})
|
||
defer finisher(&err)
|
||
|
||
maxRowID, count, err := fragment.maxRow(tx, filter)
|
||
if err != nil {
|
||
return PairField{}, nil
|
||
}
|
||
|
||
return PairField{
|
||
Pair: Pair{
|
||
ID: maxRowID,
|
||
Count: count,
|
||
},
|
||
Field: fieldName,
|
||
}, nil
|
||
}
|
||
|
||
// executeTopN executes a TopN() call.
|
||
// This first performs the TopN() to determine the top results and then
|
||
// requeries to retrieve the full counts for each of the top results.
|
||
func (e *executor) executeTopN(ctx context.Context, qcx *Qcx, index string, c *pql.Call, shards []uint64, opt *execOptions) (*PairsField, error) {
|
||
span, ctx := tracing.StartSpanFromContext(ctx, "Executor.executeTopN")
|
||
defer span.Finish()
|
||
|
||
idsArg, _, err := c.UintSliceArg("ids")
|
||
if err != nil {
|
||
return nil, fmt.Errorf("executeTopN: %v", err)
|
||
}
|
||
|
||
fieldName, _ := c.Args["_field"].(string)
|
||
n, _, err := c.UintArg("n")
|
||
if err != nil {
|
||
return nil, fmt.Errorf("executeTopN: %v", err)
|
||
}
|
||
|
||
// Execute original query.
|
||
pairs, err := e.executeTopNShards(ctx, qcx, index, c, shards, opt)
|
||
if err != nil {
|
||
return nil, errors.Wrap(err, "finding top results")
|
||
}
|
||
|
||
// If this call is against specific ids, or we didn't get results,
|
||
// or we are part of a larger distributed query then don't refetch.
|
||
if len(pairs.Pairs) == 0 || len(idsArg) > 0 || opt.Remote {
|
||
return &PairsField{
|
||
Pairs: pairs.Pairs,
|
||
Field: fieldName,
|
||
}, nil
|
||
}
|
||
// Only the original caller should refetch the full counts.
|
||
// TODO(@kuba--): ...but do we really need `Clone` here?
|
||
other := c.Clone()
|
||
|
||
ids := Pairs(pairs.Pairs).Keys()
|
||
sort.Sort(uint64Slice(ids))
|
||
other.Args["ids"] = ids
|
||
|
||
trimmedList, err := e.executeTopNShards(ctx, qcx, index, other, shards, opt)
|
||
if err != nil {
|
||
return nil, errors.Wrap(err, "retrieving full counts")
|
||
}
|
||
|
||
if n != 0 && int(n) < len(trimmedList.Pairs) {
|
||
trimmedList.Pairs = trimmedList.Pairs[0:n]
|
||
}
|
||
|
||
return &PairsField{
|
||
Pairs: trimmedList.Pairs,
|
||
Field: fieldName,
|
||
}, nil
|
||
}
|
||
|
||
func (e *executor) executeTopNShards(ctx context.Context, qcx *Qcx, index string, c *pql.Call, shards []uint64, opt *execOptions) (*PairsField, error) {
|
||
span, ctx := tracing.StartSpanFromContext(ctx, "Executor.executeTopNShards")
|
||
defer span.Finish()
|
||
|
||
// Execute calls in bulk on each remote node and merge.
|
||
mapFn := func(ctx context.Context, shard uint64) (_ interface{}, err error) {
|
||
return e.executeTopNShard(ctx, qcx, index, c, shard)
|
||
}
|
||
|
||
// Merge returned results at coordinating node.
|
||
reduceFn := func(ctx context.Context, prev, v interface{}) interface{} {
|
||
other, _ := prev.(*PairsField)
|
||
vpf, _ := v.(*PairsField)
|
||
if other == nil {
|
||
return vpf
|
||
} else if vpf == nil {
|
||
return other
|
||
}
|
||
if err := ctx.Err(); err != nil {
|
||
return err
|
||
}
|
||
other.Pairs = Pairs(other.Pairs).Add(vpf.Pairs)
|
||
return other
|
||
}
|
||
|
||
other, err := e.mapReduce(ctx, index, shards, c, opt, mapFn, reduceFn)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
results, _ := other.(*PairsField)
|
||
|
||
// Sort final merged results.
|
||
sort.Sort(Pairs(results.Pairs))
|
||
|
||
return results, nil
|
||
}
|
||
|
||
// executeTopNShard executes a TopN call for a single shard.
|
||
func (e *executor) executeTopNShard(ctx context.Context, qcx *Qcx, index string, c *pql.Call, shard uint64) (_ *PairsField, err0 error) {
|
||
span, ctx := tracing.StartSpanFromContext(ctx, "Executor.executeTopNShard")
|
||
defer span.Finish()
|
||
|
||
fieldName, _ := c.Args["_field"].(string)
|
||
n, _, err := c.UintArg("n")
|
||
if err != nil {
|
||
return nil, fmt.Errorf("executeTopNShard: %v", err)
|
||
} else if f := e.Holder.Field(index, fieldName); f != nil && (f.Type() == FieldTypeInt || f.Type() == FieldTypeDecimal) {
|
||
return nil, fmt.Errorf("cannot compute TopN() on integer field: %q", fieldName)
|
||
}
|
||
|
||
attrName, _ := c.Args["attrName"].(string)
|
||
rowIDs, _, err := c.UintSliceArg("ids")
|
||
if err != nil {
|
||
return nil, fmt.Errorf("executeTopNShard: %v", err)
|
||
}
|
||
minThreshold, _, err := c.UintArg("threshold")
|
||
if err != nil {
|
||
return nil, fmt.Errorf("executeTopNShard: %v", err)
|
||
}
|
||
attrValues, _ := c.Args["attrValues"].([]interface{})
|
||
tanimotoThreshold, _, err := c.UintArg("tanimotoThreshold")
|
||
if err != nil {
|
||
return nil, fmt.Errorf("executeTopNShard: %v", err)
|
||
}
|
||
|
||
// Retrieve bitmap used to intersect.
|
||
var src *Row
|
||
if len(c.Children) == 1 {
|
||
row, err := e.executeBitmapCallShard(ctx, qcx, index, c.Children[0], shard)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
src = row
|
||
} else if len(c.Children) > 1 {
|
||
return nil, errors.New("TopN() can only have one input bitmap")
|
||
}
|
||
|
||
// Set default field.
|
||
if fieldName == "" {
|
||
fieldName = defaultField
|
||
}
|
||
|
||
f := e.Holder.fragment(index, fieldName, viewStandard, shard)
|
||
if f == nil {
|
||
return &PairsField{}, nil
|
||
} else if f.CacheType == CacheTypeNone {
|
||
return nil, fmt.Errorf("cannot compute TopN(), field has no cache: %q", fieldName)
|
||
}
|
||
|
||
if minThreshold == 0 {
|
||
minThreshold = defaultMinThreshold
|
||
}
|
||
|
||
if tanimotoThreshold > 100 {
|
||
return nil, errors.New("Tanimoto Threshold is from 1 to 100 only")
|
||
}
|
||
|
||
idx := e.Holder.Index(index)
|
||
tx, finisher := qcx.GetTx(Txo{Write: !writable, Fragment: f, Index: idx, Shard: shard})
|
||
defer finisher(&err0)
|
||
|
||
pairs, err := f.top(tx, topOptions{
|
||
N: int(n),
|
||
Src: src,
|
||
RowIDs: rowIDs,
|
||
FilterName: attrName,
|
||
FilterValues: attrValues,
|
||
MinThreshold: minThreshold,
|
||
TanimotoThreshold: tanimotoThreshold,
|
||
})
|
||
if err != nil {
|
||
return nil, errors.Wrap(err, "getting top")
|
||
}
|
||
|
||
return &PairsField{
|
||
Pairs: pairs,
|
||
}, nil
|
||
}
|
||
|
||
// executeDifferenceShard executes a difference() call for a local shard.
|
||
func (e *executor) executeDifferenceShard(ctx context.Context, qcx *Qcx, index string, c *pql.Call, shard uint64) (_ *Row, err error) {
|
||
span, ctx := tracing.StartSpanFromContext(ctx, "Executor.executeDifferenceShard")
|
||
defer span.Finish()
|
||
|
||
var other *Row
|
||
if len(c.Children) == 0 {
|
||
return nil, fmt.Errorf("empty Difference query is currently not supported")
|
||
}
|
||
for i, input := range c.Children {
|
||
row, err := e.executeBitmapCallShard(ctx, qcx, index, input, shard)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
if i == 0 {
|
||
other = row
|
||
} else {
|
||
other = other.Difference(row)
|
||
}
|
||
}
|
||
other.invalidateCount()
|
||
return other, nil
|
||
}
|
||
|
||
// RowIdentifiers is a return type for a list of
|
||
// row ids or row keys. The names `Rows` and `Keys`
|
||
// are meant to follow the same convention as the
|
||
// Row query which returns `Columns` and `Keys`.
|
||
// TODO: Rename this to something better. Anything.
|
||
type RowIdentifiers struct {
|
||
Rows []uint64 `json:"rows"`
|
||
Keys []string `json:"keys,omitempty"`
|
||
field string
|
||
}
|
||
|
||
func (r *RowIdentifiers) Clone() (clone *RowIdentifiers) {
|
||
clone = &RowIdentifiers{
|
||
field: r.field,
|
||
}
|
||
if r.Rows != nil {
|
||
clone.Rows = make([]uint64, len(r.Rows))
|
||
copy(clone.Rows, r.Rows)
|
||
}
|
||
if r.Keys != nil {
|
||
clone.Keys = make([]string, len(r.Keys))
|
||
copy(clone.Keys, r.Keys)
|
||
}
|
||
return
|
||
}
|
||
|
||
// ToTable implements the ToTabler interface.
|
||
func (r RowIdentifiers) ToTable() (*pb.TableResponse, error) {
|
||
var n int
|
||
if len(r.Keys) > 0 {
|
||
n = len(r.Keys)
|
||
} else {
|
||
n = len(r.Rows)
|
||
}
|
||
return pb.RowsToTable(&r, n)
|
||
}
|
||
|
||
// ToRows implements the ToRowser interface.
|
||
func (r RowIdentifiers) ToRows(callback func(*pb.RowResponse) error) error {
|
||
if len(r.Keys) > 0 {
|
||
ci := []*pb.ColumnInfo{{Name: r.Field(), Datatype: "string"}}
|
||
for _, key := range r.Keys {
|
||
if err := callback(&pb.RowResponse{
|
||
Headers: ci,
|
||
Columns: []*pb.ColumnResponse{
|
||
&pb.ColumnResponse{ColumnVal: &pb.ColumnResponse_StringVal{StringVal: key}},
|
||
}}); err != nil {
|
||
return errors.Wrap(err, "calling callback")
|
||
}
|
||
ci = nil
|
||
}
|
||
} else {
|
||
ci := []*pb.ColumnInfo{{Name: r.Field(), Datatype: "uint64"}}
|
||
for _, id := range r.Rows {
|
||
if err := callback(&pb.RowResponse{
|
||
Headers: ci,
|
||
Columns: []*pb.ColumnResponse{
|
||
&pb.ColumnResponse{ColumnVal: &pb.ColumnResponse_Uint64Val{Uint64Val: uint64(id)}},
|
||
}}); err != nil {
|
||
return errors.Wrap(err, "calling callback")
|
||
}
|
||
ci = nil
|
||
}
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// Field returns the field name associated to the row.
|
||
func (r *RowIdentifiers) Field() string {
|
||
return r.field
|
||
}
|
||
|
||
// RowIDs is a query return type for just uint64 row ids.
|
||
// It should only be used internally (since RowIdentifiers
|
||
// is the external return type), but it is exported because
|
||
// the proto package needs access to it.
|
||
type RowIDs []uint64
|
||
|
||
func (r RowIDs) merge(other RowIDs, limit int) RowIDs {
|
||
i, j := 0, 0
|
||
result := make(RowIDs, 0)
|
||
for i < len(r) && j < len(other) && len(result) < limit {
|
||
av, bv := r[i], other[j]
|
||
if av < bv {
|
||
result = append(result, av)
|
||
i++
|
||
} else if av > bv {
|
||
result = append(result, bv)
|
||
j++
|
||
} else {
|
||
result = append(result, bv)
|
||
i++
|
||
j++
|
||
}
|
||
}
|
||
for i < len(r) && len(result) < limit {
|
||
result = append(result, r[i])
|
||
i++
|
||
}
|
||
for j < len(other) && len(result) < limit {
|
||
result = append(result, other[j])
|
||
j++
|
||
}
|
||
return result
|
||
}
|
||
|
||
func (e *executor) executeGroupBy(ctx context.Context, qcx *Qcx, index string, c *pql.Call, shards []uint64, opt *execOptions) ([]GroupCount, error) {
|
||
span, ctx := tracing.StartSpanFromContext(ctx, "Executor.executeGroupBy")
|
||
defer span.Finish()
|
||
// validate call
|
||
if len(c.Children) == 0 {
|
||
return nil, errors.New("need at least one child call")
|
||
}
|
||
limit := int(^uint(0) >> 1)
|
||
if lim, hasLimit, err := c.UintArg("limit"); err != nil {
|
||
return nil, err
|
||
} else if hasLimit {
|
||
limit = int(lim)
|
||
}
|
||
filter, _, err := c.CallArg("filter")
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
idx := e.Holder.Index(index)
|
||
if idx == nil {
|
||
return nil, newNotFoundError(ErrIndexNotFound, index)
|
||
}
|
||
|
||
// perform necessary Rows queries (any that have limit or columns args) -
|
||
// TODO, call async? would only help if multiple Rows queries had a column
|
||
// or limit arg.
|
||
// TODO support TopN in here would be really cool - and pretty easy I think.
|
||
bases := make(map[int]int64)
|
||
childRows := make([]RowIDs, len(c.Children))
|
||
for i, child := range c.Children {
|
||
// Check "field" first for backwards compatibility, then set _field.
|
||
// TODO: remove at Pilosa 2.0
|
||
if fieldName, ok := child.Args["field"].(string); ok {
|
||
child.Args["_field"] = fieldName
|
||
}
|
||
|
||
if child.Name != "Rows" {
|
||
return nil, errors.Errorf("'%s' is not a valid child query for GroupBy, must be 'Rows'", child.Name)
|
||
}
|
||
_, hasLimit, err := child.UintArg("limit")
|
||
if err != nil {
|
||
return nil, errors.Wrap(err, "getting limit")
|
||
}
|
||
_, hasCol, err := child.UintArg("column")
|
||
if err != nil {
|
||
return nil, errors.Wrap(err, "getting column")
|
||
}
|
||
fieldName, ok := child.Args["_field"].(string)
|
||
if !ok {
|
||
return nil, errors.Errorf("%s call must have field with valid (string) field name. Got %v of type %[2]T", child.Name, child.Args["_field"])
|
||
}
|
||
f := idx.Field(fieldName)
|
||
if f == nil {
|
||
return nil, newNotFoundError(ErrFieldNotFound, fieldName)
|
||
}
|
||
switch f.Type() {
|
||
case FieldTypeInt:
|
||
bases[i] = f.bsiGroup(f.name).Base
|
||
}
|
||
|
||
if hasLimit || hasCol { // we need to perform this query cluster-wide ahead of executeGroupByShard
|
||
childRows[i], err = e.executeRows(ctx, qcx, index, child, shards, opt)
|
||
if err != nil {
|
||
return nil, errors.Wrap(err, "getting rows for ")
|
||
}
|
||
if len(childRows[i]) == 0 { // there are no results because this field has no values.
|
||
return []GroupCount{}, nil
|
||
}
|
||
}
|
||
}
|
||
|
||
// Execute calls in bulk on each remote node and merge.
|
||
mapFn := func(ctx context.Context, shard uint64) (_ interface{}, err error) {
|
||
return e.executeGroupByShard(ctx, qcx, index, c, filter, shard, childRows, bases)
|
||
}
|
||
// Merge returned results at coordinating node.
|
||
reduceFn := func(ctx context.Context, prev, v interface{}) interface{} {
|
||
other, _ := prev.([]GroupCount)
|
||
if err := ctx.Err(); err != nil {
|
||
return err
|
||
}
|
||
return mergeGroupCounts(other, v.([]GroupCount), limit)
|
||
}
|
||
// Get full result set.
|
||
other, err := e.mapReduce(ctx, index, shards, c, opt, mapFn, reduceFn)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
results, _ := other.([]GroupCount)
|
||
|
||
// Apply having.
|
||
if having, hasHaving, err := c.CallArg("having"); err != nil {
|
||
return nil, err
|
||
} else if hasHaving {
|
||
// parse the condition as PQL
|
||
if having.Name != "Condition" {
|
||
return nil, errors.New("the only supported having call is Condition()")
|
||
}
|
||
if len(having.Args) != 1 {
|
||
return nil, errors.New("Condition() must contain a single condition")
|
||
}
|
||
for subj, cond := range having.Args {
|
||
switch subj {
|
||
case "count", "sum":
|
||
results = applyConditionToGroupCounts(results, subj, cond.(*pql.Condition))
|
||
default:
|
||
return nil, errors.New("Condition() only supports count or sum")
|
||
}
|
||
}
|
||
}
|
||
|
||
// Apply offset.
|
||
if offset, hasOffset, err := c.UintArg("offset"); err != nil {
|
||
return nil, err
|
||
} else if hasOffset {
|
||
if int(offset) < len(results) {
|
||
results = results[offset:]
|
||
}
|
||
}
|
||
// Apply limit.
|
||
if limit, hasLimit, err := c.UintArg("limit"); err != nil {
|
||
return nil, err
|
||
} else if hasLimit {
|
||
if int(limit) < len(results) {
|
||
results = results[:limit]
|
||
}
|
||
}
|
||
return results, nil
|
||
}
|
||
|
||
// FieldRow is used to distinguish rows in a group by result.
|
||
type FieldRow struct {
|
||
Field string `json:"field"`
|
||
RowID uint64 `json:"rowID"`
|
||
RowKey string `json:"rowKey,omitempty"`
|
||
Value *int64 `json:"value,omitempty"`
|
||
}
|
||
|
||
func (fr *FieldRow) Clone() (clone *FieldRow) {
|
||
clone = &FieldRow{
|
||
Field: fr.Field,
|
||
RowID: fr.RowID,
|
||
RowKey: fr.RowKey,
|
||
}
|
||
if fr.Value != nil {
|
||
// deep copy, for safety.
|
||
v := *fr.Value
|
||
clone.Value = &v
|
||
}
|
||
return
|
||
}
|
||
|
||
// MarshalJSON marshals FieldRow to JSON such that
|
||
// either a Key or an ID is included.
|
||
func (fr FieldRow) MarshalJSON() ([]byte, error) {
|
||
if fr.Value != nil {
|
||
return json.Marshal(struct {
|
||
Field string `json:"field"`
|
||
Value int64 `json:"value"`
|
||
}{
|
||
Field: fr.Field,
|
||
Value: *fr.Value,
|
||
})
|
||
}
|
||
|
||
if fr.RowKey != "" {
|
||
return json.Marshal(struct {
|
||
Field string `json:"field"`
|
||
RowKey string `json:"rowKey"`
|
||
}{
|
||
Field: fr.Field,
|
||
RowKey: fr.RowKey,
|
||
})
|
||
}
|
||
|
||
return json.Marshal(struct {
|
||
Field string `json:"field"`
|
||
RowID uint64 `json:"rowID"`
|
||
}{
|
||
Field: fr.Field,
|
||
RowID: fr.RowID,
|
||
})
|
||
}
|
||
|
||
// String is the FieldRow stringer.
|
||
func (fr FieldRow) String() string {
|
||
if fr.Value != nil {
|
||
return fmt.Sprintf("%s.%d.%d.%s", fr.Field, fr.RowID, *fr.Value, fr.RowKey)
|
||
}
|
||
return fmt.Sprintf("%s.%d.%s", fr.Field, fr.RowID, fr.RowKey)
|
||
}
|
||
|
||
// GroupCounts is a list of GroupCount.
|
||
type GroupCounts []GroupCount
|
||
|
||
// ToTable implements the ToTabler interface.
|
||
func (g GroupCounts) ToTable() (*pb.TableResponse, error) {
|
||
return pb.RowsToTable(&g, len(g))
|
||
}
|
||
|
||
// ToRows implements the ToRowser interface.
|
||
func (g GroupCounts) ToRows(callback func(*pb.RowResponse) error) error {
|
||
for i, gc := range g {
|
||
var ci []*pb.ColumnInfo
|
||
if i == 0 {
|
||
for _, fieldRow := range gc.Group {
|
||
if fieldRow.RowKey != "" {
|
||
ci = append(ci, &pb.ColumnInfo{Name: fieldRow.Field, Datatype: "string"})
|
||
} else if fieldRow.Value != nil {
|
||
ci = append(ci, &pb.ColumnInfo{Name: fieldRow.Field, Datatype: "int64"})
|
||
} else {
|
||
ci = append(ci, &pb.ColumnInfo{Name: fieldRow.Field, Datatype: "uint64"})
|
||
}
|
||
}
|
||
ci = append(ci, &pb.ColumnInfo{Name: "count", Datatype: "uint64"})
|
||
ci = append(ci, &pb.ColumnInfo{Name: "sum", Datatype: "int64"})
|
||
}
|
||
rowResp := &pb.RowResponse{
|
||
Headers: ci,
|
||
Columns: []*pb.ColumnResponse{},
|
||
}
|
||
|
||
for _, fieldRow := range gc.Group {
|
||
if fieldRow.RowKey != "" {
|
||
rowResp.Columns = append(rowResp.Columns, &pb.ColumnResponse{ColumnVal: &pb.ColumnResponse_StringVal{StringVal: fieldRow.RowKey}})
|
||
} else if fieldRow.Value != nil {
|
||
rowResp.Columns = append(rowResp.Columns, &pb.ColumnResponse{ColumnVal: &pb.ColumnResponse_Int64Val{Int64Val: *fieldRow.Value}})
|
||
} else {
|
||
rowResp.Columns = append(rowResp.Columns, &pb.ColumnResponse{ColumnVal: &pb.ColumnResponse_Uint64Val{Uint64Val: fieldRow.RowID}})
|
||
}
|
||
}
|
||
rowResp.Columns = append(rowResp.Columns,
|
||
&pb.ColumnResponse{ColumnVal: &pb.ColumnResponse_Uint64Val{Uint64Val: gc.Count}},
|
||
&pb.ColumnResponse{ColumnVal: &pb.ColumnResponse_Int64Val{Int64Val: gc.Sum}},
|
||
)
|
||
if err := callback(rowResp); err != nil {
|
||
return errors.Wrap(err, "calling callback")
|
||
}
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// GroupCount represents a result item for a group by query.
|
||
type GroupCount struct {
|
||
Group []FieldRow `json:"group"`
|
||
Count uint64 `json:"count"`
|
||
Sum int64 `json:"sum"`
|
||
}
|
||
|
||
func (g *GroupCount) Clone() (r *GroupCount) {
|
||
r = &GroupCount{
|
||
Group: make([]FieldRow, len(g.Group)),
|
||
Count: g.Count,
|
||
Sum: g.Sum,
|
||
}
|
||
for i := range g.Group {
|
||
r.Group[i] = *(g.Group[i].Clone())
|
||
}
|
||
return
|
||
}
|
||
|
||
// mergeGroupCounts merges two slices of GroupCounts throwing away any that go
|
||
// beyond the limit. It assume that the two slices are sorted by the row ids in
|
||
// the fields of the group counts. It may modify its arguments.
|
||
func mergeGroupCounts(a, b []GroupCount, limit int) []GroupCount {
|
||
if limit > len(a)+len(b) {
|
||
limit = len(a) + len(b)
|
||
}
|
||
ret := make([]GroupCount, 0, limit)
|
||
i, j := 0, 0
|
||
for i < len(a) && j < len(b) && len(ret) < limit {
|
||
switch a[i].Compare(b[j]) {
|
||
case -1:
|
||
ret = append(ret, a[i])
|
||
i++
|
||
case 0:
|
||
a[i].Count += b[j].Count
|
||
a[i].Sum += b[j].Sum
|
||
ret = append(ret, a[i])
|
||
i++
|
||
j++
|
||
case 1:
|
||
ret = append(ret, b[j])
|
||
j++
|
||
}
|
||
}
|
||
for ; i < len(a) && len(ret) < limit; i++ {
|
||
ret = append(ret, a[i])
|
||
}
|
||
for ; j < len(b) && len(ret) < limit; j++ {
|
||
ret = append(ret, b[j])
|
||
}
|
||
return ret
|
||
}
|
||
|
||
// Compare is used in ordering two GroupCount objects.
|
||
func (g GroupCount) Compare(o GroupCount) int {
|
||
for i, g1 := range g.Group {
|
||
g2 := o.Group[i]
|
||
|
||
if g1.Value != nil && g2.Value != nil {
|
||
if *g1.Value < *g2.Value {
|
||
return -1
|
||
}
|
||
if *g1.Value > *g2.Value {
|
||
return 1
|
||
}
|
||
} else {
|
||
if g1.RowID < g2.RowID {
|
||
return -1
|
||
}
|
||
if g1.RowID > g2.RowID {
|
||
return 1
|
||
}
|
||
}
|
||
}
|
||
return 0
|
||
}
|
||
|
||
func (g GroupCount) satisfiesCondition(subj string, cond *pql.Condition) bool {
|
||
switch subj {
|
||
case "count":
|
||
switch cond.Op {
|
||
case pql.EQ, pql.NEQ, pql.LT, pql.LTE, pql.GT, pql.GTE:
|
||
val, ok := cond.Uint64Value()
|
||
if !ok {
|
||
return false
|
||
}
|
||
if cond.Op == pql.EQ {
|
||
if g.Count == val {
|
||
return true
|
||
}
|
||
} else if cond.Op == pql.NEQ {
|
||
if g.Count != val {
|
||
return true
|
||
}
|
||
} else if cond.Op == pql.LT {
|
||
if g.Count < val {
|
||
return true
|
||
}
|
||
} else if cond.Op == pql.LTE {
|
||
if g.Count <= val {
|
||
return true
|
||
}
|
||
} else if cond.Op == pql.GT {
|
||
if g.Count > val {
|
||
return true
|
||
}
|
||
} else if cond.Op == pql.GTE {
|
||
if g.Count >= val {
|
||
return true
|
||
}
|
||
}
|
||
case pql.BETWEEN, pql.BTWN_LT_LTE, pql.BTWN_LTE_LT, pql.BTWN_LT_LT:
|
||
val, ok := cond.Uint64SliceValue()
|
||
if !ok {
|
||
return false
|
||
}
|
||
if cond.Op == pql.BETWEEN {
|
||
if val[0] <= g.Count && g.Count <= val[1] {
|
||
return true
|
||
}
|
||
} else if cond.Op == pql.BTWN_LT_LTE {
|
||
if val[0] < g.Count && g.Count <= val[1] {
|
||
return true
|
||
}
|
||
} else if cond.Op == pql.BTWN_LTE_LT {
|
||
if val[0] <= g.Count && g.Count < val[1] {
|
||
return true
|
||
}
|
||
} else if cond.Op == pql.BTWN_LT_LT {
|
||
if val[0] < g.Count && g.Count < val[1] {
|
||
return true
|
||
}
|
||
}
|
||
}
|
||
case "sum":
|
||
switch cond.Op {
|
||
case pql.EQ, pql.NEQ, pql.LT, pql.LTE, pql.GT, pql.GTE:
|
||
val, ok := cond.Int64Value()
|
||
if !ok {
|
||
return false
|
||
}
|
||
if cond.Op == pql.EQ {
|
||
if g.Sum == val {
|
||
return true
|
||
}
|
||
} else if cond.Op == pql.NEQ {
|
||
if g.Sum != val {
|
||
return true
|
||
}
|
||
} else if cond.Op == pql.LT {
|
||
if g.Sum < val {
|
||
return true
|
||
}
|
||
} else if cond.Op == pql.LTE {
|
||
if g.Sum <= val {
|
||
return true
|
||
}
|
||
} else if cond.Op == pql.GT {
|
||
if g.Sum > val {
|
||
return true
|
||
}
|
||
} else if cond.Op == pql.GTE {
|
||
if g.Sum >= val {
|
||
return true
|
||
}
|
||
}
|
||
case pql.BETWEEN, pql.BTWN_LT_LTE, pql.BTWN_LTE_LT, pql.BTWN_LT_LT:
|
||
val, ok := cond.Int64SliceValue()
|
||
if !ok {
|
||
return false
|
||
}
|
||
if cond.Op == pql.BETWEEN {
|
||
if val[0] <= g.Sum && g.Sum <= val[1] {
|
||
return true
|
||
}
|
||
} else if cond.Op == pql.BTWN_LT_LTE {
|
||
if val[0] < g.Sum && g.Sum <= val[1] {
|
||
return true
|
||
}
|
||
} else if cond.Op == pql.BTWN_LTE_LT {
|
||
if val[0] <= g.Sum && g.Sum < val[1] {
|
||
return true
|
||
}
|
||
} else if cond.Op == pql.BTWN_LT_LT {
|
||
if val[0] < g.Sum && g.Sum < val[1] {
|
||
return true
|
||
}
|
||
}
|
||
}
|
||
}
|
||
return false
|
||
}
|
||
|
||
// applyConditionToGroupCounts filters the contents of gcs according
|
||
// to the condition. Currently, `count` and `sum` are the only
|
||
// fields supported.
|
||
func applyConditionToGroupCounts(gcs []GroupCount, subj string, cond *pql.Condition) []GroupCount {
|
||
var i int
|
||
for _, gc := range gcs {
|
||
if !gc.satisfiesCondition(subj, cond) {
|
||
continue // drop this GroupCount
|
||
}
|
||
gcs[i] = gc
|
||
i++
|
||
}
|
||
return gcs[:i]
|
||
}
|
||
|
||
func (e *executor) executeGroupByShard(ctx context.Context, qcx *Qcx, index string, c *pql.Call, filter *pql.Call, shard uint64, childRows []RowIDs, bases map[int]int64) (_ []GroupCount, err error) {
|
||
span, ctx := tracing.StartSpanFromContext(ctx, "Executor.executeGroupByShard")
|
||
defer span.Finish()
|
||
|
||
var filterRow *Row
|
||
if filter != nil {
|
||
if filterRow, err = e.executeBitmapCallShard(ctx, qcx, index, filter, shard); err != nil {
|
||
return nil, errors.Wrapf(err, "executing group by filter for shard %d", shard)
|
||
}
|
||
}
|
||
|
||
aggregate, _, err := c.CallArg("aggregate")
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
newspan, ctx := tracing.StartSpanFromContext(ctx, "Executor.executeGroupByShard_newGroupByIterator")
|
||
iter, err := newGroupByIterator(e, qcx, childRows, c.Children, aggregate, filterRow, index, shard, e.Holder)
|
||
newspan.Finish()
|
||
|
||
if err != nil {
|
||
return nil, errors.Wrapf(err, "getting group by iterator for shard %d", shard)
|
||
}
|
||
if iter == nil {
|
||
return []GroupCount{}, nil
|
||
}
|
||
|
||
limit := int(^uint(0) >> 1)
|
||
if lim, hasLimit, err := c.UintArg("limit"); err != nil {
|
||
return nil, err
|
||
} else if hasLimit {
|
||
limit = int(lim)
|
||
}
|
||
|
||
results := make([]GroupCount, 0)
|
||
|
||
num := 0
|
||
for gc, done, err := iter.Next(ctx); !done && num < limit; gc, done, err = iter.Next(ctx) {
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
if gc.Count > 0 {
|
||
num++
|
||
results = append(results, gc)
|
||
}
|
||
}
|
||
|
||
// Apply bases.
|
||
for i, base := range bases {
|
||
for _, r := range results {
|
||
*r.Group[i].Value += base
|
||
}
|
||
}
|
||
|
||
return results, nil
|
||
}
|
||
|
||
func (e *executor) executeRows(ctx context.Context, qcx *Qcx, index string, c *pql.Call, shards []uint64, opt *execOptions) (RowIDs, error) {
|
||
|
||
// Fetch field name from argument.
|
||
// Check "field" first for backwards compatibility.
|
||
// TODO: remove at Pilosa 2.0
|
||
var fieldName string
|
||
var ok bool
|
||
if fieldName, ok = c.Args["field"].(string); ok {
|
||
c.Args["_field"] = fieldName
|
||
}
|
||
if fieldName, ok = c.Args["_field"].(string); !ok {
|
||
return nil, errors.New("Rows() field required")
|
||
}
|
||
if columnID, ok, err := c.UintArg("column"); err != nil {
|
||
return nil, errors.Wrap(err, "getting column")
|
||
} else if ok {
|
||
shards = []uint64{columnID / ShardWidth}
|
||
}
|
||
|
||
// Execute calls in bulk on each remote node and merge.
|
||
mapFn := func(ctx context.Context, shard uint64) (_ interface{}, err error) {
|
||
return e.executeRowsShard(ctx, qcx, index, fieldName, c, shard)
|
||
}
|
||
|
||
// Determine limit so we can use it when reducing.
|
||
limit := int(^uint(0) >> 1)
|
||
if lim, hasLimit, err := c.UintArg("limit"); err != nil {
|
||
return nil, err
|
||
} else if hasLimit {
|
||
limit = int(lim)
|
||
}
|
||
|
||
// Merge returned results at coordinating node.
|
||
reduceFn := func(ctx context.Context, prev, v interface{}) interface{} {
|
||
other, _ := prev.(RowIDs)
|
||
if err := ctx.Err(); err != nil {
|
||
return err
|
||
}
|
||
return other.merge(v.(RowIDs), limit)
|
||
}
|
||
// Get full result set.
|
||
other, err := e.mapReduce(ctx, index, shards, c, opt, mapFn, reduceFn)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
results, _ := other.(RowIDs)
|
||
return results, nil
|
||
}
|
||
|
||
func (e *executor) executeRowsShard(ctx context.Context, qcx *Qcx, index string, fieldName string, c *pql.Call, shard uint64) (_ RowIDs, err0 error) {
|
||
// Fetch index.
|
||
idx := e.Holder.Index(index)
|
||
if idx == nil {
|
||
return nil, newNotFoundError(ErrIndexNotFound, index)
|
||
}
|
||
// Fetch field.
|
||
f := e.Holder.Field(index, fieldName)
|
||
if f == nil {
|
||
return nil, newNotFoundError(ErrFieldNotFound, fieldName)
|
||
}
|
||
|
||
// rowIDs is the result set.
|
||
var rowIDs RowIDs
|
||
|
||
// views contains the list of views to inspect (and merge)
|
||
// in order to represent `Rows` for the field.
|
||
var views = []string{viewStandard}
|
||
|
||
// Handle `int` and `time` fields.
|
||
switch f.Type() {
|
||
case FieldTypeInt:
|
||
return nil, errors.New("int fields not supported by Rows() query")
|
||
case FieldTypeTime:
|
||
var err error
|
||
|
||
// Parse "from" time, if set.
|
||
var fromTime time.Time
|
||
if v, ok := c.Args["from"]; ok {
|
||
if fromTime, err = parseTime(v); err != nil {
|
||
return nil, errors.Wrap(err, "parsing from time")
|
||
}
|
||
}
|
||
|
||
// Parse "to" time, if set.
|
||
var toTime time.Time
|
||
if v, ok := c.Args["to"]; ok {
|
||
if toTime, err = parseTime(v); err != nil {
|
||
return nil, errors.Wrap(err, "parsing to time")
|
||
}
|
||
}
|
||
|
||
// Calculate the views for a range as long as some piece of the range
|
||
// (from/to) are specified, or if there's no standard view to represent
|
||
// all dates.
|
||
if !fromTime.IsZero() || !toTime.IsZero() || f.options.NoStandardView {
|
||
// If no quantum exists then return an empty result set.
|
||
q := f.TimeQuantum()
|
||
if q == "" {
|
||
return rowIDs, nil
|
||
}
|
||
|
||
// Get min/max based on existing views.
|
||
var vs []string
|
||
for _, v := range f.views() {
|
||
vs = append(vs, v.name)
|
||
}
|
||
min, max := minMaxViews(vs, q)
|
||
|
||
// If min/max are empty, there were no time views.
|
||
if min == "" || max == "" {
|
||
return rowIDs, nil
|
||
}
|
||
|
||
// Convert min/max from string to time.Time.
|
||
minTime, err := timeOfView(min, false)
|
||
if err != nil {
|
||
return rowIDs, errors.Wrapf(err, "getting min time from view: %s", min)
|
||
}
|
||
if fromTime.IsZero() || fromTime.Before(minTime) {
|
||
fromTime = minTime
|
||
}
|
||
|
||
maxTime, err := timeOfView(max, true)
|
||
if err != nil {
|
||
return rowIDs, errors.Wrapf(err, "getting max time from view: %s", max)
|
||
}
|
||
if toTime.IsZero() || toTime.After(maxTime) {
|
||
toTime = maxTime
|
||
}
|
||
|
||
// Determine the views based on the specified time range.
|
||
views = viewsByTimeRange(viewStandard, fromTime, toTime, q)
|
||
}
|
||
}
|
||
|
||
start := uint64(0)
|
||
if previous, ok, err := c.UintArg("previous"); err != nil {
|
||
return nil, errors.Wrap(err, "getting previous")
|
||
} else if ok {
|
||
start = previous + 1
|
||
}
|
||
|
||
filters := []rowFilter{}
|
||
if columnID, ok, err := c.UintArg("column"); err != nil {
|
||
return nil, err
|
||
} else if ok {
|
||
colShard := columnID >> shardwidth.Exponent
|
||
if colShard != shard {
|
||
return rowIDs, nil
|
||
}
|
||
filters = append(filters, filterColumn(columnID))
|
||
}
|
||
|
||
limit := int(^uint(0) >> 1)
|
||
if lim, hasLimit, err := c.UintArg("limit"); err != nil {
|
||
return nil, errors.Wrap(err, "getting limit")
|
||
} else if hasLimit {
|
||
filters = append(filters, filterWithLimit(lim))
|
||
limit = int(lim)
|
||
}
|
||
|
||
var likeErr chan error
|
||
if like, hasLike, err := c.StringArg("like"); err != nil {
|
||
return nil, errors.Wrap(err, "getting like pattern")
|
||
} else if hasLike {
|
||
likeErr = make(chan error, 1)
|
||
filters = append(filters, filterLike(like, f.TranslateStore(), likeErr))
|
||
}
|
||
|
||
tx, finisher := qcx.GetTx(Txo{Write: !writable, Index: idx, Shard: shard})
|
||
defer finisher(&err0)
|
||
|
||
for _, view := range views {
|
||
if err := ctx.Err(); err != nil {
|
||
return nil, err
|
||
}
|
||
frag := e.Holder.fragment(index, fieldName, view, shard)
|
||
if frag == nil {
|
||
continue
|
||
}
|
||
|
||
viewRows, err := frag.rows(ctx, tx, start, filters...)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
select {
|
||
case err = <-likeErr:
|
||
return nil, err
|
||
default:
|
||
}
|
||
rowIDs = rowIDs.merge(viewRows, limit)
|
||
}
|
||
|
||
return rowIDs, nil
|
||
}
|
||
|
||
type ExtractedTableField struct {
|
||
Name string `json:"name"`
|
||
Type string `json:"type"`
|
||
}
|
||
|
||
type KeyOrID struct {
|
||
ID uint64
|
||
Key string
|
||
Keyed bool
|
||
}
|
||
|
||
func (kid KeyOrID) MarshalJSON() ([]byte, error) {
|
||
if kid.Keyed {
|
||
return json.Marshal(kid.Key)
|
||
}
|
||
|
||
return json.Marshal(kid.ID)
|
||
}
|
||
|
||
type ExtractedTableColumn struct {
|
||
Column KeyOrID `json:"column"`
|
||
Rows []interface{} `json:"rows"`
|
||
}
|
||
|
||
type ExtractedTable struct {
|
||
Fields []ExtractedTableField `json:"fields"`
|
||
Columns []ExtractedTableColumn `json:"columns"`
|
||
}
|
||
|
||
// ToRows implements the ToRowser interface.
|
||
func (t ExtractedTable) ToRows(callback func(*pb.RowResponse) error) error {
|
||
if len(t.Columns) == 0 {
|
||
return nil
|
||
}
|
||
|
||
headers := make([]*pb.ColumnInfo, len(t.Fields)+1)
|
||
colType := "uint64"
|
||
if t.Columns[0].Column.Keyed {
|
||
colType = "string"
|
||
}
|
||
headers[0] = &pb.ColumnInfo{
|
||
Name: "_id",
|
||
Datatype: colType,
|
||
}
|
||
dataHeaders := headers[1:]
|
||
for i, f := range t.Fields {
|
||
dataHeaders[i] = &pb.ColumnInfo{
|
||
Name: f.Name,
|
||
Datatype: f.Type,
|
||
}
|
||
}
|
||
|
||
for _, c := range t.Columns {
|
||
cols := make([]*pb.ColumnResponse, len(c.Rows)+1)
|
||
if c.Column.Keyed {
|
||
cols[0] = &pb.ColumnResponse{
|
||
ColumnVal: &pb.ColumnResponse_StringVal{
|
||
StringVal: c.Column.Key,
|
||
},
|
||
}
|
||
} else {
|
||
cols[0] = &pb.ColumnResponse{
|
||
ColumnVal: &pb.ColumnResponse_Uint64Val{
|
||
Uint64Val: c.Column.ID,
|
||
},
|
||
}
|
||
}
|
||
valCols := cols[1:]
|
||
for i, r := range c.Rows {
|
||
var col *pb.ColumnResponse
|
||
switch r := r.(type) {
|
||
case nil:
|
||
col = &pb.ColumnResponse{}
|
||
case bool:
|
||
col = &pb.ColumnResponse{
|
||
ColumnVal: &pb.ColumnResponse_BoolVal{
|
||
BoolVal: r,
|
||
},
|
||
}
|
||
case int64:
|
||
col = &pb.ColumnResponse{
|
||
ColumnVal: &pb.ColumnResponse_Int64Val{
|
||
Int64Val: r,
|
||
},
|
||
}
|
||
case uint64:
|
||
col = &pb.ColumnResponse{
|
||
ColumnVal: &pb.ColumnResponse_Uint64Val{
|
||
Uint64Val: r,
|
||
},
|
||
}
|
||
case string:
|
||
col = &pb.ColumnResponse{
|
||
ColumnVal: &pb.ColumnResponse_StringVal{
|
||
StringVal: r,
|
||
},
|
||
}
|
||
case []uint64:
|
||
col = &pb.ColumnResponse{
|
||
ColumnVal: &pb.ColumnResponse_Uint64ArrayVal{
|
||
Uint64ArrayVal: &pb.Uint64Array{
|
||
Vals: r,
|
||
},
|
||
},
|
||
}
|
||
case []string:
|
||
col = &pb.ColumnResponse{
|
||
ColumnVal: &pb.ColumnResponse_StringArrayVal{
|
||
StringArrayVal: &pb.StringArray{
|
||
Vals: r,
|
||
},
|
||
},
|
||
}
|
||
case pql.Decimal:
|
||
col = &pb.ColumnResponse{
|
||
ColumnVal: &pb.ColumnResponse_DecimalVal{
|
||
DecimalVal: &pb.Decimal{
|
||
Value: r.Value,
|
||
Scale: r.Scale,
|
||
},
|
||
},
|
||
}
|
||
default:
|
||
return errors.Errorf("unsupported field value: %v (type: %T)", r, r)
|
||
}
|
||
valCols[i] = col
|
||
}
|
||
err := callback(&pb.RowResponse{
|
||
Headers: headers,
|
||
Columns: cols,
|
||
})
|
||
if err != nil {
|
||
return err
|
||
}
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// ToTable converts the table to protobuf format.
|
||
func (t ExtractedTable) ToTable() (*pb.TableResponse, error) {
|
||
return pb.RowsToTable(t, len(t.Columns))
|
||
}
|
||
|
||
type ExtractedIDColumn struct {
|
||
ColumnID uint64
|
||
Rows [][]uint64
|
||
}
|
||
|
||
type ExtractedIDMatrix struct {
|
||
Fields []string
|
||
Columns []ExtractedIDColumn
|
||
}
|
||
|
||
func (e *ExtractedIDMatrix) Append(m ExtractedIDMatrix) {
|
||
e.Columns = append(e.Columns, m.Columns...)
|
||
if e.Fields == nil {
|
||
e.Fields = m.Fields
|
||
}
|
||
}
|
||
|
||
func (e *executor) executeExtract(ctx context.Context, qcx *Qcx, index string, c *pql.Call, shards []uint64, opt *execOptions) (ExtractedIDMatrix, error) {
|
||
// Extract the column filter call.
|
||
if len(c.Children) < 1 {
|
||
return ExtractedIDMatrix{}, errors.New("missing column filter in Extract")
|
||
}
|
||
filter := c.Children[0]
|
||
|
||
// Extract fields from rows calls.
|
||
fields := make([]string, len(c.Children)-1)
|
||
for i, rows := range c.Children[1:] {
|
||
if rows.Name != "Rows" {
|
||
return ExtractedIDMatrix{}, errors.Errorf("child call of Extract is %q but expected Rows", rows.Name)
|
||
}
|
||
var fieldName string
|
||
var ok bool
|
||
for k, v := range rows.Args {
|
||
switch k {
|
||
case "field", "_field":
|
||
fieldName = v.(string)
|
||
ok = true
|
||
default:
|
||
return ExtractedIDMatrix{}, errors.Errorf("unsupported Rows argument for Extract: %q", k)
|
||
}
|
||
}
|
||
if !ok {
|
||
return ExtractedIDMatrix{}, errors.New("missing field specification in Rows")
|
||
}
|
||
fields[i] = fieldName
|
||
}
|
||
|
||
// Execute calls in bulk on each remote node and merge.
|
||
mapFn := func(ctx context.Context, shard uint64) (_ interface{}, err error) {
|
||
return e.executeExtractShard(ctx, qcx, index, fields, filter, shard)
|
||
}
|
||
|
||
// Merge returned results at coordinating node.
|
||
reduceFn := func(ctx context.Context, prev, v interface{}) interface{} {
|
||
other, _ := prev.(ExtractedIDMatrix)
|
||
if err := ctx.Err(); err != nil {
|
||
return err
|
||
}
|
||
other.Append(v.(ExtractedIDMatrix))
|
||
return other
|
||
}
|
||
|
||
// Get full result set.
|
||
other, err := e.mapReduce(ctx, index, shards, c, opt, mapFn, reduceFn)
|
||
if err != nil {
|
||
return ExtractedIDMatrix{}, err
|
||
}
|
||
results, _ := other.(ExtractedIDMatrix)
|
||
sort.Slice(results.Columns, func(i, j int) bool {
|
||
return results.Columns[i].ColumnID < results.Columns[j].ColumnID
|
||
})
|
||
return results, nil
|
||
}
|
||
|
||
func mergeBits(bits *Row, mask uint64, out map[uint64]uint64) {
|
||
for _, v := range bits.Columns() {
|
||
out[v] |= mask
|
||
}
|
||
}
|
||
|
||
var trueRowFakeID = []uint64{1}
|
||
var falseRowFakeID = []uint64{0}
|
||
|
||
func (e *executor) executeExtractShard(ctx context.Context, qcx *Qcx, index string, fields []string, filter *pql.Call, shard uint64) (_ ExtractedIDMatrix, err0 error) {
|
||
|
||
// Execute filter.
|
||
colsBitmap, err := e.executeBitmapCallShard(ctx, qcx, index, filter, shard)
|
||
if err != nil {
|
||
return ExtractedIDMatrix{}, errors.Wrap(err, "failed to get extraction column filter")
|
||
}
|
||
|
||
// Fetch index.
|
||
idx := e.Holder.Index(index)
|
||
if idx == nil {
|
||
return ExtractedIDMatrix{}, newNotFoundError(ErrIndexNotFound, index)
|
||
}
|
||
|
||
tx, finisher := qcx.GetTx(Txo{Write: !writable, Index: idx, Shard: shard})
|
||
defer finisher(&err0)
|
||
|
||
// Decompress columns bitmap.
|
||
cols := colsBitmap.Columns()
|
||
|
||
// Generate a matrix to stuff the results into.
|
||
m := make([]ExtractedIDColumn, len(cols))
|
||
{
|
||
rowsBuf := make([][]uint64, len(m)*len(fields))
|
||
for i, c := range cols {
|
||
m[i] = ExtractedIDColumn{
|
||
ColumnID: c,
|
||
Rows: rowsBuf[i*len(fields) : (i+1)*len(fields) : (i+1)*len(fields)],
|
||
}
|
||
}
|
||
}
|
||
if len(m) == 0 {
|
||
return ExtractedIDMatrix{
|
||
Fields: fields,
|
||
Columns: m,
|
||
}, nil
|
||
}
|
||
mLookup := make(map[uint64]int)
|
||
for i, j := range cols {
|
||
mLookup[j] = i
|
||
}
|
||
|
||
// Process fields.
|
||
for i, name := range fields {
|
||
// Look up the field.
|
||
field := idx.Field(name)
|
||
if field == nil {
|
||
return ExtractedIDMatrix{}, newNotFoundError(ErrFieldNotFound, name)
|
||
}
|
||
|
||
switch field.Type() {
|
||
case FieldTypeSet, FieldTypeMutex, FieldTypeTime:
|
||
// Handle a set field by listing the rows and then intersecting them with the filter.
|
||
|
||
// Extract the standard view fragment.
|
||
fragment := e.Holder.fragment(index, name, viewStandard, shard)
|
||
if fragment == nil {
|
||
// There is nothing here.
|
||
continue
|
||
}
|
||
|
||
// List all rows in the standard view.
|
||
rows, err := fragment.rows(ctx, tx, 0)
|
||
if err != nil {
|
||
return ExtractedIDMatrix{}, errors.Wrap(err, "listing rows in set field")
|
||
}
|
||
|
||
// Loop over each row and scan the intersection with the filter.
|
||
for _, rowID := range rows {
|
||
// Load row from fragment.
|
||
row, err := fragment.row(tx, rowID)
|
||
if err != nil {
|
||
return ExtractedIDMatrix{}, errors.Wrap(err, "loading row from fragment")
|
||
}
|
||
|
||
// Apply column filter to row.
|
||
row = row.Intersect(colsBitmap)
|
||
|
||
// Rotate vector into the matrix.
|
||
for _, columnID := range row.Columns() {
|
||
fieldSlot := &m[mLookup[columnID]].Rows[i]
|
||
*fieldSlot = append(*fieldSlot, rowID)
|
||
}
|
||
}
|
||
case FieldTypeBool:
|
||
// Handle bool fields by scanning the true and false rows and assigning an integer.
|
||
|
||
// Extract the standard view fragment.
|
||
fragment := e.Holder.fragment(index, name, viewStandard, shard)
|
||
if fragment == nil {
|
||
// There is nothing here.
|
||
continue
|
||
}
|
||
|
||
// Fetch true and false rows.
|
||
trueRow, err := fragment.row(tx, trueRowID)
|
||
if err != nil {
|
||
return ExtractedIDMatrix{}, errors.Wrap(err, "loading true row from fragment")
|
||
}
|
||
falseRow, err := fragment.row(tx, falseRowID)
|
||
if err != nil {
|
||
return ExtractedIDMatrix{}, errors.Wrap(err, "loading true row from fragment")
|
||
}
|
||
|
||
// Fetch values by column.
|
||
for j := range m {
|
||
col := m[j].ColumnID
|
||
switch {
|
||
case trueRow.Includes(col):
|
||
m[j].Rows[i] = trueRowFakeID
|
||
case falseRow.Includes(col):
|
||
m[j].Rows[i] = falseRowFakeID
|
||
}
|
||
}
|
||
|
||
case FieldTypeInt, FieldTypeDecimal:
|
||
// Handle an int/decimal field by rotating a BSI matrix.
|
||
|
||
// Extract the BSI view fragment.
|
||
fragment := e.Holder.fragment(index, name, viewBSIGroupPrefix+name, shard)
|
||
if fragment == nil {
|
||
// There is nothing here.
|
||
continue
|
||
}
|
||
|
||
// Load the BSI group.
|
||
bsig := field.bsiGroup(name)
|
||
if bsig == nil {
|
||
return ExtractedIDMatrix{}, ErrBSIGroupNotFound
|
||
}
|
||
|
||
// Load the BSI exists bit.
|
||
exists, err := fragment.row(tx, bsiExistsBit)
|
||
if err != nil {
|
||
return ExtractedIDMatrix{}, errors.Wrap(err, "loading BSI exists bit from fragment")
|
||
}
|
||
|
||
// Filter BSI exists bit by selected columns.
|
||
exists = exists.Intersect(colsBitmap)
|
||
if !exists.Any() {
|
||
// No relevant BSI values are present in this fragment.
|
||
continue
|
||
}
|
||
|
||
// Populate a map with the BSI data.
|
||
data := make(map[uint64]uint64)
|
||
mergeBits(exists, 0, data)
|
||
|
||
// Copy in the sign bit.
|
||
sign, err := fragment.row(tx, bsiSignBit)
|
||
if err != nil {
|
||
return ExtractedIDMatrix{}, errors.Wrap(err, "loading BSI sign bit from fragment")
|
||
}
|
||
sign = sign.Intersect(exists)
|
||
mergeBits(sign, 1<<63, data)
|
||
|
||
// Copy in the significand.
|
||
for i := uint(0); i < bsig.BitDepth; i++ {
|
||
bits, err := fragment.row(tx, bsiOffsetBit+uint64(i))
|
||
if err != nil {
|
||
return ExtractedIDMatrix{}, errors.Wrap(err, "loading BSI significand bit from fragment")
|
||
}
|
||
bits = bits.Intersect(exists)
|
||
mergeBits(bits, 1<<i, data)
|
||
}
|
||
|
||
// Store the results back into the matrix.
|
||
for columnID, val := range data {
|
||
// Convert to two's complement.
|
||
val = uint64((2*(int64(val)>>63) + 1) * int64(val&^(1<<63)))
|
||
|
||
m[mLookup[columnID]].Rows[i] = []uint64{val}
|
||
}
|
||
}
|
||
}
|
||
|
||
// Emit the final matrix.
|
||
// Like RowIDs, this is an internal type and will need to be converted.
|
||
return ExtractedIDMatrix{
|
||
Fields: fields,
|
||
Columns: m,
|
||
}, nil
|
||
}
|
||
|
||
func (e *executor) executeRowShard(ctx context.Context, qcx *Qcx, index string, c *pql.Call, shard uint64) (_ *Row, err0 error) {
|
||
|
||
span, _ := tracing.StartSpanFromContext(ctx, "Executor.executeRowShard")
|
||
defer span.Finish()
|
||
|
||
// Handle bsiGroup ranges differently.
|
||
if c.HasConditionArg() {
|
||
// looks the same on badger/roaring. we think.
|
||
return e.executeRowBSIGroupShard(ctx, qcx, index, c, shard)
|
||
}
|
||
|
||
// Fetch index.
|
||
idx := e.Holder.Index(index)
|
||
if idx == nil {
|
||
return nil, newNotFoundError(ErrIndexNotFound, index)
|
||
}
|
||
|
||
// Fetch field name from argument.
|
||
fieldName, err := c.FieldArg()
|
||
if err != nil {
|
||
return nil, errors.New("Row() argument required: field")
|
||
}
|
||
f := idx.Field(fieldName)
|
||
if f == nil {
|
||
return nil, newNotFoundError(ErrFieldNotFound, fieldName)
|
||
}
|
||
|
||
// Parse "from" time, if set.
|
||
var fromTime time.Time
|
||
if v, ok := c.Args["from"]; ok {
|
||
if fromTime, err = parseTime(v); err != nil {
|
||
return nil, errors.Wrap(err, "parsing from time")
|
||
}
|
||
}
|
||
|
||
// Parse "to" time, if set.
|
||
var toTime time.Time
|
||
if v, ok := c.Args["to"]; ok {
|
||
if toTime, err = parseTime(v); err != nil {
|
||
return nil, errors.Wrap(err, "parsing to time")
|
||
}
|
||
}
|
||
|
||
timeNotSet := fromTime.IsZero() && toTime.IsZero()
|
||
|
||
// This is workaround to support pql.ASSIGN ('=') as condition ('==') for int and decimal fields
|
||
if c.Name == "Row" && timeNotSet &&
|
||
(f.Type() == FieldTypeInt || f.Type() == FieldTypeDecimal) {
|
||
// re-write args as conditions for fieldName
|
||
for k, v := range c.Args {
|
||
if _, ok := v.(*pql.Condition); k == fieldName && !ok {
|
||
c.Args[k] = &pql.Condition{
|
||
Op: pql.EQ,
|
||
Value: v,
|
||
}
|
||
|
||
return e.executeRowBSIGroupShard(ctx, qcx, index, c, shard)
|
||
}
|
||
}
|
||
}
|
||
|
||
rowID, rowOK, rowErr := c.UintArg(fieldName)
|
||
if rowErr != nil {
|
||
return nil, fmt.Errorf("Row() error with arg for row: %v", rowErr)
|
||
} else if !rowOK {
|
||
return nil, fmt.Errorf("Row() must specify %v", rowLabel)
|
||
}
|
||
|
||
// Simply return row if times are not set.
|
||
if c.Name == "Row" && timeNotSet {
|
||
|
||
frag := e.Holder.fragment(index, fieldName, viewStandard, shard)
|
||
if frag == nil {
|
||
return NewRow(), nil
|
||
}
|
||
|
||
tx, finisher := qcx.GetTx(Txo{Write: !writable, Fragment: frag, Index: idx, Shard: shard})
|
||
defer finisher(&err0)
|
||
return frag.row(tx, rowID)
|
||
}
|
||
|
||
// If no quantum exists then return an empty bitmap.
|
||
q := f.TimeQuantum()
|
||
if q == "" {
|
||
return &Row{}, nil
|
||
}
|
||
|
||
// Set maximum "to" value if only "from" is set. We don't need to worry
|
||
// about setting the minimum "from" since it is the zero value if omitted.
|
||
if toTime.IsZero() {
|
||
// Set the end timestamp to current time + 1 day, in order to account for timezone differences.
|
||
toTime = time.Now().AddDate(0, 0, 1)
|
||
}
|
||
|
||
// Union bitmaps across all time-based views.
|
||
views := viewsByTimeRange(viewStandard, fromTime, toTime, q)
|
||
rows := make([]*Row, 0, len(views))
|
||
for _, view := range views {
|
||
f := e.Holder.fragment(index, fieldName, view, shard)
|
||
if f == nil {
|
||
continue
|
||
}
|
||
tx, finisher := qcx.GetTx(Txo{Write: !writable, Fragment: f, Index: idx, Shard: shard})
|
||
defer finisher(&err0)
|
||
|
||
row, err := f.row(tx, rowID)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
rows = append(rows, row)
|
||
}
|
||
if len(rows) == 0 {
|
||
return &Row{}, nil
|
||
} else if len(rows) == 1 {
|
||
return rows[0], nil
|
||
}
|
||
row := rows[0].Union(rows[1:]...)
|
||
return row, nil
|
||
|
||
}
|
||
|
||
// executeRowBSIGroupShard executes a range(bsiGroup) call for a local shard.
|
||
func (e *executor) executeRowBSIGroupShard(ctx context.Context, qcx *Qcx, index string, c *pql.Call, shard uint64) (_ *Row, err0 error) {
|
||
|
||
span, _ := tracing.StartSpanFromContext(ctx, "Executor.executeRowBSIGroupShard")
|
||
defer span.Finish()
|
||
|
||
// Only one conditional should be present.
|
||
if len(c.Args) == 0 {
|
||
return nil, errors.New("Row(): condition required")
|
||
} else if len(c.Args) > 1 {
|
||
return nil, errors.New("Row(): too many arguments")
|
||
}
|
||
|
||
// Extract conditional.
|
||
var fieldName string
|
||
var cond *pql.Condition
|
||
for k, v := range c.Args {
|
||
vv, ok := v.(*pql.Condition)
|
||
if !ok {
|
||
return nil, fmt.Errorf("Row(): %q: expected condition argument, got %v", k, v)
|
||
}
|
||
fieldName, cond = k, vv
|
||
}
|
||
|
||
f := e.Holder.Field(index, fieldName)
|
||
if f == nil {
|
||
return nil, newNotFoundError(ErrFieldNotFound, fieldName)
|
||
}
|
||
|
||
tx, finisher := qcx.GetTx(Txo{Write: !writable, Index: f.idx, Shard: shard})
|
||
defer finisher(&err0)
|
||
|
||
// EQ null _exists - frag.NotNull()
|
||
// NEQ null frag.NotNull()
|
||
// BETWEEN a,b(in) BETWEEN/frag.RowBetween()
|
||
// BETWEEN a,b(out) BETWEEN/frag.NotNull()
|
||
// EQ <int> frag.RangeOp
|
||
// NEQ <int> frag.RangeOp
|
||
|
||
// Handle `!= null` and `== null`.
|
||
if cond.Op == pql.NEQ && cond.Value == nil {
|
||
// Retrieve fragment.
|
||
frag := e.Holder.fragment(index, fieldName, viewBSIGroupPrefix+fieldName, shard)
|
||
if frag == nil {
|
||
return NewRow(), nil
|
||
}
|
||
return frag.notNull(tx)
|
||
|
||
} else if cond.Op == pql.EQ && cond.Value == nil {
|
||
// Make sure the index supports existence tracking.
|
||
idx := e.Holder.Index(index)
|
||
if idx == nil {
|
||
return nil, newNotFoundError(ErrIndexNotFound, index)
|
||
} else if idx.existenceField() == nil {
|
||
return nil, errors.Errorf("index does not support existence tracking: %s", index)
|
||
}
|
||
|
||
var existenceRow *Row
|
||
existenceFrag := e.Holder.fragment(index, existenceFieldName, viewStandard, shard)
|
||
if existenceFrag == nil {
|
||
existenceRow = NewRow()
|
||
} else {
|
||
if existenceRow, err0 = existenceFrag.row(tx, 0); err0 != nil {
|
||
return nil, err0
|
||
}
|
||
}
|
||
|
||
var notNull *Row
|
||
var err error
|
||
|
||
// Retrieve notNull from fragment if it exists.
|
||
if frag := e.Holder.fragment(index, fieldName, viewBSIGroupPrefix+fieldName, shard); frag != nil {
|
||
if notNull, err = frag.notNull(tx); err != nil {
|
||
return nil, errors.Wrap(err, "getting fragment not null")
|
||
}
|
||
} else {
|
||
notNull = NewRow()
|
||
}
|
||
|
||
return existenceRow.Difference(notNull), nil
|
||
|
||
} else if cond.Op == pql.BETWEEN || cond.Op == pql.BTWN_LT_LT ||
|
||
cond.Op == pql.BTWN_LTE_LT || cond.Op == pql.BTWN_LT_LTE {
|
||
predicates, err := getCondIntSlice(f, cond)
|
||
if err != nil {
|
||
return nil, errors.Wrap(err, "getting condition value")
|
||
}
|
||
|
||
// Only support two integers for the between operation.
|
||
if len(predicates) != 2 {
|
||
return nil, errors.New("Row(): BETWEEN condition requires exactly two integer values")
|
||
}
|
||
|
||
// The reason we don't just call:
|
||
// return f.RowBetween(fieldName, predicates[0], predicates[1])
|
||
// here is because we need the call to be shard-specific.
|
||
|
||
// Find bsiGroup.
|
||
bsig := f.bsiGroup(fieldName)
|
||
if bsig == nil {
|
||
return nil, ErrBSIGroupNotFound
|
||
}
|
||
|
||
baseValueMin, baseValueMax, outOfRange := bsig.baseValueBetween(predicates[0], predicates[1])
|
||
if outOfRange {
|
||
return NewRow(), nil
|
||
}
|
||
|
||
// Retrieve fragment.
|
||
frag := e.Holder.fragment(index, fieldName, viewBSIGroupPrefix+fieldName, shard)
|
||
if frag == nil {
|
||
return NewRow(), nil
|
||
}
|
||
|
||
// If the query is asking for the entire valid range, just return
|
||
// the not-null bitmap for the bsiGroup.
|
||
if predicates[0] <= bsig.Min && predicates[1] >= bsig.Max {
|
||
return frag.notNull(tx)
|
||
}
|
||
|
||
return frag.rangeBetween(tx, bsig.BitDepth, baseValueMin, baseValueMax)
|
||
|
||
} else {
|
||
value, err := getScaledInt(f, cond.Value)
|
||
if err != nil {
|
||
return nil, errors.Wrap(err, "getting scaled integer")
|
||
}
|
||
|
||
// Find bsiGroup.
|
||
bsig := f.bsiGroup(fieldName)
|
||
if bsig == nil {
|
||
return nil, ErrBSIGroupNotFound
|
||
}
|
||
|
||
baseValue, outOfRange := bsig.baseValue(cond.Op, value)
|
||
if outOfRange && cond.Op != pql.NEQ {
|
||
return NewRow(), nil
|
||
}
|
||
|
||
// Retrieve fragment.
|
||
frag := e.Holder.fragment(index, fieldName, viewBSIGroupPrefix+fieldName, shard)
|
||
if frag == nil {
|
||
return NewRow(), nil
|
||
}
|
||
|
||
// LT[E] and GT[E] should return all not-null if selected range fully encompasses valid bsiGroup range.
|
||
if (cond.Op == pql.LT && value > bsig.Max) || (cond.Op == pql.LTE && value >= bsig.Max) ||
|
||
(cond.Op == pql.GT && value < bsig.Min) || (cond.Op == pql.GTE && value <= bsig.Min) {
|
||
return frag.notNull(tx)
|
||
}
|
||
|
||
// outOfRange for NEQ should return all not-null.
|
||
if outOfRange && cond.Op == pql.NEQ {
|
||
return frag.notNull(tx)
|
||
}
|
||
|
||
return frag.rangeOp(tx, cond.Op, bsig.BitDepth, baseValue)
|
||
}
|
||
}
|
||
|
||
// executeIntersectShard executes a intersect() call for a local shard.
|
||
func (e *executor) executeIntersectShard(ctx context.Context, qcx *Qcx, index string, c *pql.Call, shard uint64) (_ *Row, err error) {
|
||
span, ctx := tracing.StartSpanFromContext(ctx, "Executor.executeIntersectShard")
|
||
defer span.Finish()
|
||
|
||
var other *Row
|
||
if len(c.Children) == 0 {
|
||
return nil, fmt.Errorf("empty Intersect query is currently not supported")
|
||
}
|
||
for i, input := range c.Children {
|
||
row, err := e.executeBitmapCallShard(ctx, qcx, index, input, shard)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
if i == 0 {
|
||
other = row
|
||
} else {
|
||
other = other.Intersect(row)
|
||
}
|
||
}
|
||
other.invalidateCount()
|
||
return other, nil
|
||
}
|
||
|
||
// executeUnionShard executes a union() call for a local shard.
|
||
func (e *executor) executeUnionShard(ctx context.Context, qcx *Qcx, index string, c *pql.Call, shard uint64) (_ *Row, err error) {
|
||
span, ctx := tracing.StartSpanFromContext(ctx, "Executor.executeUnionShard")
|
||
defer span.Finish()
|
||
|
||
other := NewRow()
|
||
for i, input := range c.Children {
|
||
row, err := e.executeBitmapCallShard(ctx, qcx, index, input, shard)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
if i == 0 {
|
||
other = row
|
||
} else {
|
||
other = other.Union(row)
|
||
}
|
||
}
|
||
other.invalidateCount()
|
||
return other, nil
|
||
}
|
||
|
||
// executeXorShard executes a xor() call for a local shard.
|
||
func (e *executor) executeXorShard(ctx context.Context, qcx *Qcx, index string, c *pql.Call, shard uint64) (_ *Row, err error) {
|
||
span, ctx := tracing.StartSpanFromContext(ctx, "Executor.executeXorShard")
|
||
defer span.Finish()
|
||
|
||
other := NewRow()
|
||
for i, input := range c.Children {
|
||
row, err := e.executeBitmapCallShard(ctx, qcx, index, input, shard)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
if i == 0 {
|
||
other = row
|
||
} else {
|
||
other = other.Xor(row)
|
||
}
|
||
}
|
||
other.invalidateCount()
|
||
return other, nil
|
||
}
|
||
|
||
// executePrecomputedCallShard pretends to execute a precomputed call for a local shard.
|
||
func (e *executor) executePrecomputedCallShard(ctx context.Context, qcx *Qcx, index string, c *pql.Call, shard uint64) (_ *Row, err error) {
|
||
if c.Precomputed != nil {
|
||
v := c.Precomputed[shard]
|
||
if v == nil {
|
||
return NewRow(), nil
|
||
}
|
||
if r, ok := v.(*Row); ok {
|
||
if r != nil {
|
||
return r, nil
|
||
} else {
|
||
return NewRow(), nil
|
||
}
|
||
}
|
||
return nil, fmt.Errorf("precomputed value is not a row: %T", v)
|
||
}
|
||
return nil, fmt.Errorf("per-shard: missing precomputed values for shard %d", shard)
|
||
}
|
||
|
||
// executeNotShard executes a Not() call for a local shard.
|
||
func (e *executor) executeNotShard(ctx context.Context, qcx *Qcx, index string, c *pql.Call, shard uint64) (_ *Row, err error) {
|
||
span, ctx := tracing.StartSpanFromContext(ctx, "Executor.executeNotShard")
|
||
defer span.Finish()
|
||
|
||
if len(c.Children) == 0 {
|
||
return nil, errors.New("Not() requires an input row")
|
||
} else if len(c.Children) > 1 {
|
||
return nil, errors.New("Not() only accepts a single row input")
|
||
}
|
||
|
||
// Make sure the index supports existence tracking.
|
||
idx := e.Holder.Index(index)
|
||
if idx == nil {
|
||
return nil, newNotFoundError(ErrIndexNotFound, index)
|
||
} else if idx.existenceField() == nil {
|
||
return nil, errors.Errorf("index does not support existence tracking: %s", index)
|
||
}
|
||
|
||
tx, finisher := qcx.GetTx(Txo{Write: !writable, Index: idx, Shard: shard})
|
||
defer finisher(&err)
|
||
|
||
var existenceRow *Row
|
||
existenceFrag := e.Holder.fragment(index, existenceFieldName, viewStandard, shard)
|
||
if existenceFrag == nil {
|
||
existenceRow = NewRow()
|
||
} else {
|
||
if existenceRow, err = existenceFrag.row(tx, 0); err != nil {
|
||
return nil, err
|
||
}
|
||
}
|
||
|
||
row, err := e.executeBitmapCallShard(ctx, qcx, index, c.Children[0], shard)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
return existenceRow.Difference(row), nil
|
||
}
|
||
|
||
// executeAllCallShard executes an All() call for a local shard.
|
||
func (e *executor) executeAllCallShard(ctx context.Context, qcx *Qcx, index string, c *pql.Call, shard uint64) (res *Row, err error) {
|
||
|
||
span, _ := tracing.StartSpanFromContext(ctx, "Executor.executeAllCallShard")
|
||
defer span.Finish()
|
||
|
||
if len(c.Children) > 0 {
|
||
return nil, errors.New("All() does not accept an input row")
|
||
}
|
||
|
||
// Make sure the index supports existence tracking.
|
||
idx := e.Holder.Index(index)
|
||
if idx == nil {
|
||
return nil, newNotFoundError(ErrIndexNotFound, index)
|
||
} else if idx.existenceField() == nil {
|
||
return nil, errors.Errorf("index does not support existence tracking: %s", index)
|
||
}
|
||
|
||
var existenceRow *Row
|
||
existenceFrag := e.Holder.fragment(index, existenceFieldName, viewStandard, shard)
|
||
if existenceFrag == nil {
|
||
existenceRow = NewRow()
|
||
} else {
|
||
tx, finisher := qcx.GetTx(Txo{Write: !writable, Index: idx, Fragment: existenceFrag, Shard: shard})
|
||
defer finisher(&err)
|
||
|
||
if existenceRow, err = existenceFrag.row(tx, 0); err != nil {
|
||
return nil, err
|
||
}
|
||
}
|
||
|
||
return existenceRow, nil
|
||
}
|
||
|
||
// executeShiftShard executes a shift() call for a local shard.
|
||
func (e *executor) executeShiftShard(ctx context.Context, qcx *Qcx, index string, c *pql.Call, shard uint64) (_ *Row, err error) {
|
||
n, _, err := c.IntArg("n")
|
||
if err != nil {
|
||
return nil, fmt.Errorf("executeShiftShard: %v", err)
|
||
}
|
||
|
||
if len(c.Children) == 0 {
|
||
return nil, errors.New("Shift() requires an input row")
|
||
} else if len(c.Children) > 1 {
|
||
return nil, errors.New("Shift() only accepts a single row input")
|
||
}
|
||
|
||
row, err := e.executeBitmapCallShard(ctx, qcx, index, c.Children[0], shard)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
return row.Shift(n)
|
||
}
|
||
|
||
// executeCount executes a count() call.
|
||
func (e *executor) executeCount(ctx context.Context, qcx *Qcx, index string, c *pql.Call, shards []uint64, opt *execOptions) (uint64, error) {
|
||
span, ctx := tracing.StartSpanFromContext(ctx, "Executor.executeCount")
|
||
defer span.Finish()
|
||
|
||
if len(c.Children) == 0 {
|
||
return 0, errors.New("Count() requires an input bitmap")
|
||
} else if len(c.Children) > 1 {
|
||
return 0, errors.New("Count() only accepts a single bitmap input")
|
||
}
|
||
|
||
// Execute calls in bulk on each remote node and merge.
|
||
mapFn := func(ctx context.Context, shard uint64) (_ interface{}, err error) {
|
||
row, err := e.executeBitmapCallShard(ctx, qcx, index, c.Children[0], shard)
|
||
if err != nil {
|
||
return 0, err
|
||
}
|
||
return row.Count(), nil
|
||
}
|
||
|
||
// Merge returned results at coordinating node.
|
||
reduceFn := func(ctx context.Context, prev, v interface{}) interface{} {
|
||
other, _ := prev.(uint64)
|
||
return other + v.(uint64)
|
||
}
|
||
|
||
result, err := e.mapReduce(ctx, index, shards, c, opt, mapFn, reduceFn)
|
||
if err != nil {
|
||
return 0, err
|
||
}
|
||
n, _ := result.(uint64)
|
||
|
||
return n, nil
|
||
}
|
||
|
||
// executeClearBit executes a Clear() call.
|
||
func (e *executor) executeClearBit(ctx context.Context, qcx *Qcx, index string, c *pql.Call, opt *execOptions) (bool, error) {
|
||
span, ctx := tracing.StartSpanFromContext(ctx, "Executor.executeClearBit")
|
||
defer span.Finish()
|
||
|
||
// Read colID
|
||
colID, ok, err := c.UintArg("_" + columnLabel)
|
||
if err != nil {
|
||
return false, fmt.Errorf("reading Clear() column: %v", err)
|
||
} else if !ok {
|
||
return false, fmt.Errorf("column argument to Clear(<COLUMN>, <FIELD>=<ROW>) required")
|
||
}
|
||
// Read field name.
|
||
fieldName, err := c.FieldArg()
|
||
if err != nil {
|
||
return false, errors.New("Clear() argument required: field")
|
||
}
|
||
|
||
// Retrieve field.
|
||
idx := e.Holder.Index(index)
|
||
if idx == nil {
|
||
return false, newNotFoundError(ErrIndexNotFound, index)
|
||
}
|
||
|
||
f := idx.Field(fieldName)
|
||
if f == nil {
|
||
return false, newNotFoundError(ErrFieldNotFound, fieldName)
|
||
}
|
||
|
||
// Int field.
|
||
if f.Type() == FieldTypeInt || f.Type() == FieldTypeDecimal {
|
||
return e.executeClearValueField(ctx, qcx, index, c, f, colID, opt)
|
||
}
|
||
|
||
rowID, ok, err := c.UintArg(fieldName)
|
||
if err != nil {
|
||
return false, fmt.Errorf("reading Clear() row: %v", err)
|
||
} else if !ok {
|
||
return false, fmt.Errorf("row=<row> argument required to Clear() call")
|
||
}
|
||
|
||
return e.executeClearBitField(ctx, qcx, index, c, f, colID, rowID, opt)
|
||
}
|
||
|
||
// executeClearBitField executes a Clear() call for a field.
|
||
func (e *executor) executeClearBitField(ctx context.Context, qcx *Qcx, index string, c *pql.Call, f *Field, colID, rowID uint64, opt *execOptions) (_ bool, err error) {
|
||
span, ctx := tracing.StartSpanFromContext(ctx, "Executor.executeClearBitField")
|
||
defer span.Finish()
|
||
|
||
shard := colID / ShardWidth
|
||
|
||
idx := e.Holder.Index(index)
|
||
|
||
tx, finisher := qcx.GetTx(Txo{Write: writable, Index: idx, Shard: shard})
|
||
defer finisher(&err)
|
||
|
||
ret := false
|
||
for _, node := range e.Cluster.shardNodes(index, shard) {
|
||
// Update locally if host matches.
|
||
if node.ID == e.Node.ID {
|
||
val, err := f.ClearBit(tx, rowID, colID)
|
||
if err != nil {
|
||
return false, err
|
||
} else if val {
|
||
ret = true
|
||
}
|
||
continue
|
||
}
|
||
// Do not forward call if this is already being forwarded.
|
||
if opt.Remote {
|
||
continue
|
||
}
|
||
|
||
// Forward call to remote node otherwise.
|
||
res, err := e.remoteExec(ctx, node, index, &pql.Query{Calls: []*pql.Call{c}}, nil, nil)
|
||
if err != nil {
|
||
return false, err
|
||
}
|
||
ret = res[0].(bool)
|
||
}
|
||
return ret, nil
|
||
}
|
||
|
||
// executeClearRow executes a ClearRow() call.
|
||
func (e *executor) executeClearRow(ctx context.Context, qcx *Qcx, index string, c *pql.Call, shards []uint64, opt *execOptions) (_ bool, err error) {
|
||
span, ctx := tracing.StartSpanFromContext(ctx, "Executor.executeClearRow")
|
||
defer span.Finish()
|
||
|
||
// Ensure the field type supports ClearRow().
|
||
var fieldName string
|
||
fieldName, err = c.FieldArg()
|
||
if err != nil {
|
||
return false, errors.New("ClearRow() argument required: field")
|
||
}
|
||
field := e.Holder.Field(index, fieldName)
|
||
if field == nil {
|
||
return false, newNotFoundError(ErrFieldNotFound, fieldName)
|
||
}
|
||
|
||
switch field.Type() {
|
||
case FieldTypeSet, FieldTypeTime, FieldTypeMutex, FieldTypeBool:
|
||
// These field types support ClearRow().
|
||
default:
|
||
return false, fmt.Errorf("ClearRow() is not supported on %s field types", field.Type())
|
||
}
|
||
|
||
// Execute calls in bulk on each remote node and merge.
|
||
mapFn := func(ctx context.Context, shard uint64) (_ interface{}, err error) {
|
||
return e.executeClearRowShard(ctx, qcx, index, c, shard)
|
||
}
|
||
|
||
// Merge returned results at coordinating node.
|
||
reduceFn := func(ctx context.Context, prev, v interface{}) interface{} {
|
||
val, ok := v.(bool)
|
||
if !ok {
|
||
return errors.Errorf("executeClearRow.reduceFn: val is non-bool (%+v)", v)
|
||
}
|
||
if prev == nil || val {
|
||
return val
|
||
}
|
||
pval, ok := prev.(bool)
|
||
if !ok {
|
||
return errors.Errorf("executeClearRow.reduceFn: prev is non-bool (%+v)", prev)
|
||
}
|
||
return pval
|
||
}
|
||
|
||
result, err := e.mapReduce(ctx, index, shards, c, opt, mapFn, reduceFn)
|
||
if err != nil {
|
||
return false, errors.Wrap(err, "mapreducing clearrow")
|
||
}
|
||
return result.(bool), err
|
||
}
|
||
|
||
// executeClearRowShard executes a ClearRow() call for a single shard.
|
||
func (e *executor) executeClearRowShard(ctx context.Context, qcx *Qcx, index string, c *pql.Call, shard uint64) (_ bool, err error) {
|
||
span, _ := tracing.StartSpanFromContext(ctx, "Executor.executeClearRowShard")
|
||
defer span.Finish()
|
||
|
||
var fieldName string
|
||
fieldName, err = c.FieldArg()
|
||
if err != nil {
|
||
return false, errors.New("ClearRow() argument required: field")
|
||
}
|
||
|
||
// Read fields using labels.
|
||
var rowID uint64
|
||
var ok bool
|
||
rowID, ok, err = c.UintArg(fieldName)
|
||
if err != nil {
|
||
return false, fmt.Errorf("reading ClearRow() row: %v", err)
|
||
} else if !ok {
|
||
return false, fmt.Errorf("ClearRow() row argument '%v' required", rowLabel)
|
||
}
|
||
|
||
field := e.Holder.Field(index, fieldName)
|
||
if field == nil {
|
||
return false, newNotFoundError(ErrFieldNotFound, fieldName)
|
||
}
|
||
|
||
idx := e.Holder.Index(index)
|
||
tx, finisher := qcx.GetTx(Txo{Write: writable, Index: idx, Shard: shard})
|
||
defer finisher(&err)
|
||
|
||
// Remove the row from all views.
|
||
changed := false
|
||
for _, view := range field.views() {
|
||
fragment := e.Holder.fragment(index, fieldName, view.name, shard)
|
||
if fragment == nil {
|
||
continue
|
||
}
|
||
cleared, err := fragment.clearRow(tx, rowID)
|
||
if err != nil {
|
||
return false, errors.Wrapf(err, "clearing row %d on view %s shard %d", rowID, view.name, shard)
|
||
}
|
||
changed = changed || cleared
|
||
}
|
||
|
||
return changed, nil
|
||
}
|
||
|
||
// executeSetRow executes a Store() call.
|
||
|
||
func (e *executor) executeSetRow(ctx context.Context, qcx *Qcx, indexName string, c *pql.Call, shards []uint64, opt *execOptions) (bool, error) {
|
||
// Parse arguments.
|
||
fieldName, err := c.FieldArg()
|
||
if err != nil {
|
||
return false, errors.New("field required for Store()")
|
||
}
|
||
argKey, argKeyed := c.Args[fieldName].(string)
|
||
|
||
field := e.Holder.Field(indexName, fieldName)
|
||
if field == nil {
|
||
// Find index.
|
||
idx := e.Holder.Index(indexName)
|
||
if idx == nil {
|
||
return false, newNotFoundError(ErrIndexNotFound, indexName)
|
||
}
|
||
|
||
// Create field.
|
||
opts := []FieldOption{OptFieldTypeSet(CacheTypeNone, 0)}
|
||
if argKeyed {
|
||
opts = append(opts, OptFieldKeys())
|
||
}
|
||
field, err = idx.CreateField(fieldName, opts...)
|
||
if err != nil {
|
||
// We wrap these because we want to indicate that it wasn't found,
|
||
// but also the problem we encountered trying to create it.
|
||
return false, newNotFoundError(errors.Wrap(err, "creating field"), fieldName)
|
||
}
|
||
}
|
||
// Ensure the field type supports Store().
|
||
if field.Type() != FieldTypeSet {
|
||
return false, fmt.Errorf("can't Store() on a %s field", field.Type())
|
||
}
|
||
|
||
if argKeyed {
|
||
// Translate the argument key.
|
||
if !field.Keys() {
|
||
return false, errors.New("can't Store() a key into an unkeyed field")
|
||
}
|
||
id, err := field.TranslateStore().TranslateKey(argKey, true)
|
||
if err != nil {
|
||
return false, errors.Wrap(err, "translating a key for Store()")
|
||
}
|
||
c.Args[fieldName] = id
|
||
}
|
||
|
||
// Execute calls in bulk on each remote node and merge.
|
||
mapFn := func(ctx context.Context, shard uint64) (_ interface{}, err error) {
|
||
return e.executeSetRowShard(ctx, qcx, indexName, c, shard)
|
||
}
|
||
|
||
// Merge returned results at coordinating node.
|
||
reduceFn := func(ctx context.Context, prev, v interface{}) interface{} {
|
||
val, ok := v.(bool)
|
||
if !ok {
|
||
return errors.Errorf("executeSetRow.reduceFn: val is non-bool (%+v)", v)
|
||
}
|
||
if prev == nil || val {
|
||
return val
|
||
}
|
||
|
||
pval, ok := prev.(bool)
|
||
if !ok {
|
||
return errors.Errorf("executeSetRow.reduceFn: prev is non-bool (%+v)", prev)
|
||
}
|
||
return pval
|
||
}
|
||
|
||
result, err := e.mapReduce(ctx, indexName, shards, c, opt, mapFn, reduceFn)
|
||
if err != nil {
|
||
return false, err
|
||
}
|
||
|
||
b, ok := result.(bool)
|
||
if !ok {
|
||
return false, errors.New("unsupported result type")
|
||
}
|
||
return b, nil
|
||
}
|
||
|
||
// executeSetRowShard executes a SetRow() call for a single shard.
|
||
func (e *executor) executeSetRowShard(ctx context.Context, qcx *Qcx, index string, c *pql.Call, shard uint64) (_ bool, err error) {
|
||
var fieldName string
|
||
fieldName, err = c.FieldArg()
|
||
if err != nil {
|
||
return false, errors.New("Store() argument required: field")
|
||
}
|
||
|
||
// Read fields using labels.
|
||
var rowID uint64
|
||
var ok bool
|
||
rowID, ok, err = c.UintArg(fieldName)
|
||
if err != nil {
|
||
return false, fmt.Errorf("reading Store() row: %v", err)
|
||
} else if !ok {
|
||
return false, fmt.Errorf("need the <FIELD>=<ROW> argument on Store()")
|
||
}
|
||
|
||
field := e.Holder.Field(index, fieldName)
|
||
if field == nil {
|
||
return false, newNotFoundError(ErrFieldNotFound, fieldName)
|
||
}
|
||
|
||
// Retrieve source row.
|
||
var src *Row
|
||
if len(c.Children) == 1 {
|
||
row, err := e.executeBitmapCallShard(ctx, qcx, index, c.Children[0], shard)
|
||
if err != nil {
|
||
return false, errors.Wrap(err, "getting source row")
|
||
}
|
||
src = row
|
||
} else {
|
||
return false, errors.New("Store() requires a source row")
|
||
}
|
||
|
||
// Set the row on the standard view.
|
||
changed := false
|
||
fragment := e.Holder.fragment(index, fieldName, viewStandard, shard)
|
||
if fragment == nil {
|
||
// Since the destination fragment doesn't exist, create one.
|
||
view, err := field.createViewIfNotExists(viewStandard)
|
||
if err != nil {
|
||
return false, errors.Wrap(err, "creating view")
|
||
}
|
||
fragment, err = view.CreateFragmentIfNotExists(shard)
|
||
if err != nil {
|
||
return false, errors.Wrapf(err, "creating fragment: %d", shard)
|
||
}
|
||
}
|
||
|
||
idx := e.Holder.Index(index)
|
||
tx, finisher := qcx.GetTx(Txo{Write: writable, Index: idx, Shard: shard})
|
||
defer finisher(&err)
|
||
|
||
set, err := fragment.setRow(tx, src, rowID)
|
||
if err != nil {
|
||
return false, errors.Wrapf(err, "storing row %d on view %s shard %d", rowID, viewStandard, shard)
|
||
}
|
||
changed = changed || set
|
||
|
||
return changed, nil
|
||
}
|
||
|
||
// executeSet executes a Set() call.
|
||
func (e *executor) executeSet(ctx context.Context, qcx *Qcx, index string, c *pql.Call, opt *execOptions) (_ bool, err error) {
|
||
span, ctx := tracing.StartSpanFromContext(ctx, "Executor.executeSet")
|
||
defer span.Finish()
|
||
|
||
// Read colID.
|
||
colID, ok, err := c.UintArg("_" + columnLabel)
|
||
if err != nil {
|
||
return false, fmt.Errorf("reading Set() column: %v", err)
|
||
} else if !ok {
|
||
return false, fmt.Errorf("Set() column argument '%v' required", columnLabel)
|
||
}
|
||
|
||
idx := e.Holder.Index(index)
|
||
if idx == nil {
|
||
return false, ErrIndexNotFound
|
||
}
|
||
|
||
shard := colID / ShardWidth
|
||
|
||
tx, finisher := qcx.GetTx(Txo{Write: writable, Index: idx, Shard: shard})
|
||
defer finisher(&err)
|
||
|
||
// Read field name.
|
||
fieldName, err := c.FieldArg()
|
||
if err != nil {
|
||
return false, errors.New("Set() argument required: field")
|
||
}
|
||
|
||
// Retrieve field.
|
||
f := idx.Field(fieldName)
|
||
if f == nil {
|
||
return false, newNotFoundError(ErrFieldNotFound, fieldName)
|
||
}
|
||
|
||
// Set column on existence field.
|
||
if ef := idx.existenceField(); ef != nil {
|
||
if _, err := ef.SetBit(tx, 0, colID, nil); err != nil {
|
||
return false, errors.Wrap(err, "setting existence column")
|
||
}
|
||
}
|
||
finisher(nil) // commit to free of the write lock needed inside executeSetBitField
|
||
|
||
switch f.Type() {
|
||
case FieldTypeInt, FieldTypeDecimal:
|
||
// Int or Decimal field.
|
||
v, ok := c.Arg(fieldName)
|
||
if !ok {
|
||
return false, fmt.Errorf("Set() row argument '%v' required", rowLabel)
|
||
}
|
||
|
||
// Before we scale a decimal to an integer, we need to make sure the decimal
|
||
// is between min/max for the field. If it's not, converting to an integer
|
||
// can result in an overflow.
|
||
if dec, ok := v.(pql.Decimal); ok && f.Options().Type == FieldTypeDecimal {
|
||
if dec.LessThan(f.Options().Min) || dec.GreaterThan(f.Options().Max) {
|
||
return false, ErrDecimalOutOfRange
|
||
}
|
||
}
|
||
|
||
// Read row value.
|
||
rowVal, err := getScaledInt(f, v)
|
||
if err != nil {
|
||
return false, fmt.Errorf("reading Set() row (int/decimal): %v", err)
|
||
}
|
||
return e.executeSetValueField(ctx, qcx, index, c, f, colID, rowVal, opt)
|
||
|
||
default:
|
||
// Read row ID.
|
||
rowID, ok, err := c.UintArg(fieldName)
|
||
if err != nil {
|
||
return false, fmt.Errorf("reading Set() row: %v", err)
|
||
} else if !ok {
|
||
return false, fmt.Errorf("Set() row argument '%v' required", rowLabel)
|
||
}
|
||
|
||
var timestamp *time.Time
|
||
sTimestamp, ok := c.Args["_timestamp"].(string)
|
||
if ok {
|
||
t, err := time.Parse(TimeFormat, sTimestamp)
|
||
if err != nil {
|
||
return false, fmt.Errorf("invalid date: %s", sTimestamp)
|
||
}
|
||
timestamp = &t
|
||
}
|
||
|
||
return e.executeSetBitField(ctx, qcx, index, c, f, colID, rowID, timestamp, opt)
|
||
}
|
||
}
|
||
|
||
// executeSetBitField executes a Set() call for a specific field.
|
||
func (e *executor) executeSetBitField(ctx context.Context, qcx *Qcx, index string, c *pql.Call, f *Field, colID, rowID uint64, timestamp *time.Time, opt *execOptions) (_ bool, err0 error) {
|
||
span, ctx := tracing.StartSpanFromContext(ctx, "Executor.executeSetBitField")
|
||
defer span.Finish()
|
||
|
||
shard := colID / ShardWidth
|
||
ret := false
|
||
|
||
idx := e.Holder.Index(index)
|
||
tx, finisher := qcx.GetTx(Txo{Write: writable, Index: idx, Shard: shard})
|
||
defer finisher(&err0)
|
||
|
||
for _, node := range e.Cluster.shardNodes(index, shard) {
|
||
// Update locally if host matches.
|
||
if node.ID == e.Node.ID {
|
||
val, err := f.SetBit(tx, rowID, colID, timestamp)
|
||
if err != nil {
|
||
return false, err
|
||
} else if val {
|
||
ret = true
|
||
}
|
||
continue
|
||
}
|
||
|
||
// Do not forward call if this is already being forwarded.
|
||
if opt.Remote {
|
||
continue
|
||
}
|
||
|
||
// Forward call to remote node otherwise.
|
||
res, err := e.remoteExec(ctx, node, index, &pql.Query{Calls: []*pql.Call{c}}, nil, nil)
|
||
if err != nil {
|
||
return false, err
|
||
}
|
||
ret = res[0].(bool)
|
||
}
|
||
return ret, nil
|
||
}
|
||
|
||
// executeSetValueField executes a Set() call for a specific int field.
|
||
func (e *executor) executeSetValueField(ctx context.Context, qcx *Qcx, index string, c *pql.Call, f *Field, colID uint64, value int64, opt *execOptions) (_ bool, err error) {
|
||
span, ctx := tracing.StartSpanFromContext(ctx, "Executor.executeSetValueField")
|
||
defer span.Finish()
|
||
|
||
shard := colID / ShardWidth
|
||
ret := false
|
||
|
||
idx := e.Holder.Index(index)
|
||
tx, finisher := qcx.GetTx(Txo{Write: writable, Index: idx, Shard: shard})
|
||
defer finisher(&err)
|
||
|
||
for _, node := range e.Cluster.shardNodes(index, shard) {
|
||
// Update locally if host matches.
|
||
if node.ID == e.Node.ID {
|
||
val, err := f.SetValue(tx, colID, value)
|
||
if err != nil {
|
||
return false, err
|
||
} else if val {
|
||
ret = true
|
||
}
|
||
continue
|
||
}
|
||
|
||
// Do not forward call if this is already being forwarded.
|
||
if opt.Remote {
|
||
continue
|
||
}
|
||
|
||
// Forward call to remote node otherwise.
|
||
res, err := e.remoteExec(ctx, node, index, &pql.Query{Calls: []*pql.Call{c}}, nil, nil)
|
||
if err != nil {
|
||
return false, err
|
||
}
|
||
ret = res[0].(bool)
|
||
}
|
||
return ret, nil
|
||
}
|
||
|
||
// executeClearValueField removes value for colID if present
|
||
func (e *executor) executeClearValueField(ctx context.Context, qcx *Qcx, index string, c *pql.Call, f *Field, colID uint64, opt *execOptions) (_ bool, err error) {
|
||
span, ctx := tracing.StartSpanFromContext(ctx, "Executor.executeClearValueField")
|
||
defer span.Finish()
|
||
|
||
shard := colID / ShardWidth
|
||
ret := false
|
||
|
||
idx := e.Holder.Index(index)
|
||
tx, finisher := qcx.GetTx(Txo{Write: writable, Index: idx, Shard: shard})
|
||
defer finisher(&err)
|
||
|
||
for _, node := range e.Cluster.shardNodes(index, shard) {
|
||
// Update locally if host matches.
|
||
if node.ID == e.Node.ID {
|
||
val, err := f.ClearValue(tx, colID)
|
||
if err != nil {
|
||
return false, err
|
||
} else if val {
|
||
ret = true
|
||
}
|
||
continue
|
||
}
|
||
|
||
// Do not forward call if this is already being forwarded.
|
||
if opt.Remote {
|
||
continue
|
||
}
|
||
|
||
// Forward call to remote node otherwise.
|
||
res, err := e.remoteExec(ctx, node, index, &pql.Query{Calls: []*pql.Call{c}}, nil, nil)
|
||
if err != nil {
|
||
return false, err
|
||
}
|
||
ret = res[0].(bool)
|
||
}
|
||
return ret, nil
|
||
}
|
||
|
||
// executeSetRowAttrs executes a SetRowAttrs() call.
|
||
func (e *executor) executeSetRowAttrs(ctx context.Context, qcx *Qcx, index string, c *pql.Call, opt *execOptions) error {
|
||
span, ctx := tracing.StartSpanFromContext(ctx, "Executor.executeSetRowAttrs")
|
||
defer span.Finish()
|
||
|
||
fieldName, ok := c.Args["_field"].(string)
|
||
if !ok {
|
||
return errors.New("SetRowAttrs() field required")
|
||
}
|
||
|
||
// Retrieve field.
|
||
field := e.Holder.Field(index, fieldName)
|
||
if field == nil {
|
||
return newNotFoundError(ErrFieldNotFound, fieldName)
|
||
}
|
||
|
||
// Parse labels.
|
||
rowID, ok, err := c.UintArg("_" + rowLabel)
|
||
if err != nil {
|
||
return fmt.Errorf("reading SetRowAttrs() row: %v", err)
|
||
} else if !ok {
|
||
return fmt.Errorf("SetRowAttrs() row field '%v' required", rowLabel)
|
||
}
|
||
|
||
// Copy args and remove reserved fields.
|
||
attrs := pql.CopyArgs(c.Args)
|
||
delete(attrs, "_field")
|
||
delete(attrs, "_"+rowLabel)
|
||
|
||
// Set attributes.
|
||
if err := field.RowAttrStore().SetAttrs(rowID, attrs); err != nil {
|
||
return err
|
||
}
|
||
|
||
// Do not forward call if this is already being forwarded.
|
||
if opt.Remote {
|
||
return nil
|
||
}
|
||
|
||
// Execute on remote nodes in parallel.
|
||
nodes := Nodes(e.Cluster.nodes).FilterID(e.Node.ID)
|
||
resp := make(chan error, len(nodes))
|
||
for _, node := range nodes {
|
||
go func(node *Node) {
|
||
_, err := e.remoteExec(ctx, node, index, &pql.Query{Calls: []*pql.Call{c}}, nil, nil)
|
||
resp <- err
|
||
}(node)
|
||
}
|
||
|
||
// Return first error.
|
||
for range nodes {
|
||
if err := <-resp; err != nil {
|
||
return err
|
||
}
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// executeBulkSetRowAttrs executes a set of SetRowAttrs() calls.
|
||
func (e *executor) executeBulkSetRowAttrs(ctx context.Context, qcx *Qcx, index string, calls []*pql.Call, opt *execOptions) ([]interface{}, error) {
|
||
span, ctx := tracing.StartSpanFromContext(ctx, "Executor.executeBulkSetRowAttrs")
|
||
defer span.Finish()
|
||
|
||
// Collect attributes by field/id.
|
||
m := make(map[string]map[uint64]map[string]interface{})
|
||
for i, c := range calls {
|
||
if i%10 == 0 {
|
||
if err := validateQueryContext(ctx); err != nil {
|
||
return nil, err
|
||
}
|
||
}
|
||
|
||
field, ok := c.Args["_field"].(string)
|
||
if !ok {
|
||
return nil, errors.New("SetRowAttrs() field required")
|
||
}
|
||
|
||
// Retrieve field.
|
||
f := e.Holder.Field(index, field)
|
||
if f == nil {
|
||
return nil, newNotFoundError(ErrFieldNotFound, field)
|
||
}
|
||
|
||
rowID, ok, err := c.UintArg("_" + rowLabel)
|
||
if err != nil {
|
||
return nil, errors.Wrap(err, "reading SetRowAttrs() row")
|
||
} else if !ok {
|
||
return nil, fmt.Errorf("SetRowAttrs row field '%v' required", rowLabel)
|
||
}
|
||
|
||
// Copy args and remove reserved fields.
|
||
attrs := pql.CopyArgs(c.Args)
|
||
delete(attrs, "_field")
|
||
delete(attrs, "_"+rowLabel)
|
||
|
||
// Create field group, if not exists.
|
||
fieldMap := m[field]
|
||
if fieldMap == nil {
|
||
fieldMap = make(map[uint64]map[string]interface{})
|
||
m[field] = fieldMap
|
||
}
|
||
|
||
// Set or merge attributes.
|
||
attr := fieldMap[rowID]
|
||
if attr == nil {
|
||
fieldMap[rowID] = cloneAttrs(attrs)
|
||
} else {
|
||
for k, v := range attrs {
|
||
attr[k] = v
|
||
}
|
||
}
|
||
}
|
||
|
||
// Bulk insert attributes by field.
|
||
for name, fieldMap := range m {
|
||
// Retrieve field.
|
||
field := e.Holder.Field(index, name)
|
||
if field == nil {
|
||
return nil, newNotFoundError(ErrFieldNotFound, name)
|
||
}
|
||
|
||
// Set attributes.
|
||
if err := field.RowAttrStore().SetBulkAttrs(fieldMap); err != nil {
|
||
return nil, err
|
||
}
|
||
}
|
||
|
||
if !opt.Remote {
|
||
tags := []string{"index:" + index, "bulk:true"}
|
||
e.Holder.Stats.CountWithCustomTags(MetricSetRowAttrs, int64(len(m)), 1.0, tags)
|
||
}
|
||
|
||
// Do not forward call if this is already being forwarded.
|
||
if opt.Remote {
|
||
return make([]interface{}, len(calls)), nil
|
||
}
|
||
|
||
// Execute on remote nodes in parallel.
|
||
nodes := Nodes(e.Cluster.nodes).FilterID(e.Node.ID)
|
||
resp := make(chan error, len(nodes))
|
||
for _, node := range nodes {
|
||
go func(node *Node) {
|
||
_, err := e.remoteExec(ctx, node, index, &pql.Query{Calls: calls}, nil, nil)
|
||
resp <- err
|
||
}(node)
|
||
}
|
||
|
||
// Return first error.
|
||
for range nodes {
|
||
if err := <-resp; err != nil {
|
||
return nil, err
|
||
}
|
||
}
|
||
|
||
// Return a set of nil responses to match the non-optimized return.
|
||
return make([]interface{}, len(calls)), nil
|
||
}
|
||
|
||
// executeSetColumnAttrs executes a SetColumnAttrs() call.
|
||
func (e *executor) executeSetColumnAttrs(ctx context.Context, qcx *Qcx, index string, c *pql.Call, opt *execOptions) error {
|
||
span, ctx := tracing.StartSpanFromContext(ctx, "Executor.executeSetColumnAttrs")
|
||
defer span.Finish()
|
||
|
||
// Retrieve index.
|
||
idx := e.Holder.Index(index)
|
||
if idx == nil {
|
||
return newNotFoundError(ErrIndexNotFound, index)
|
||
}
|
||
|
||
col, okCol, errCol := c.UintArg("_" + columnLabel)
|
||
if errCol != nil || !okCol {
|
||
return fmt.Errorf("reading SetColumnAttrs() col errs: %v found %v", errCol, okCol)
|
||
}
|
||
|
||
// Copy args and remove reserved fields.
|
||
attrs := pql.CopyArgs(c.Args)
|
||
delete(attrs, "_"+columnLabel)
|
||
delete(attrs, "field")
|
||
|
||
// Set attributes.
|
||
|
||
if err := idx.ColumnAttrStore().SetAttrs(col, attrs); err != nil {
|
||
return err
|
||
}
|
||
// Do not forward call if this is already being forwarded.
|
||
if opt.Remote {
|
||
return nil
|
||
}
|
||
|
||
// Execute on remote nodes in parallel.
|
||
nodes := Nodes(e.Cluster.nodes).FilterID(e.Node.ID)
|
||
resp := make(chan error, len(nodes))
|
||
for _, node := range nodes {
|
||
go func(node *Node) {
|
||
_, err := e.remoteExec(ctx, node, index, &pql.Query{Calls: []*pql.Call{c}}, nil, nil)
|
||
resp <- err
|
||
}(node)
|
||
}
|
||
|
||
// Return first error.
|
||
for range nodes {
|
||
if err := <-resp; err != nil {
|
||
return err
|
||
}
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// remoteExec executes a PQL query remotely for a set of shards on a node.
|
||
func (e *executor) remoteExec(ctx context.Context, node *Node, index string, q *pql.Query, shards []uint64, embed []*Row) (results []interface{}, err error) { // nolint: interfacer
|
||
span, ctx := tracing.StartSpanFromContext(ctx, "Executor.executeExec")
|
||
defer span.Finish()
|
||
|
||
// Encode request object.
|
||
pbreq := &QueryRequest{
|
||
Query: q.String(),
|
||
Shards: shards,
|
||
Remote: true,
|
||
EmbeddedData: embed,
|
||
}
|
||
|
||
pb, err := e.client.QueryNode(ctx, &node.URI, index, pbreq)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
return pb.Results, pb.Err
|
||
}
|
||
|
||
// shardsByNode returns a mapping of nodes to shards.
|
||
// Returns errShardUnavailable if a shard cannot be allocated to a node.
|
||
func (e *executor) shardsByNode(nodes []*Node, index string, shards []uint64) (map[*Node][]uint64, error) {
|
||
m := make(map[*Node][]uint64)
|
||
|
||
loop:
|
||
for _, shard := range shards {
|
||
for _, node := range e.Cluster.ShardNodes(index, shard) {
|
||
if Nodes(nodes).Contains(node) {
|
||
m[node] = append(m[node], shard)
|
||
continue loop
|
||
}
|
||
}
|
||
return nil, errShardUnavailable
|
||
}
|
||
return m, nil
|
||
}
|
||
|
||
// mapReduce maps and reduces data across the cluster.
|
||
//
|
||
// If a mapping of shards to a node fails then the shards are resplit across
|
||
// secondary nodes and retried. This continues to occur until all nodes are exhausted.
|
||
func (e *executor) mapReduce(ctx context.Context, index string, shards []uint64, c *pql.Call, opt *execOptions, mapFn mapFunc, reduceFn reduceFunc) (_ interface{}, err error) {
|
||
span, ctx := tracing.StartSpanFromContext(ctx, "Executor.mapReduce")
|
||
defer span.Finish()
|
||
|
||
ch := make(chan mapResponse)
|
||
|
||
// Wrap context with a cancel to kill goroutines on exit.
|
||
ctx, cancel := context.WithCancel(ctx)
|
||
defer cancel()
|
||
|
||
// If this is the coordinating node then start with all nodes in the cluster.
|
||
//
|
||
// However, if this request is being sent from the coordinator then all
|
||
// processing should be done locally so we start with just the local node.
|
||
var nodes []*Node
|
||
if !opt.Remote {
|
||
nodes = Nodes(e.Cluster.nodes).Clone()
|
||
} else {
|
||
nodes = []*Node{e.Cluster.nodeByID(e.Node.ID)}
|
||
}
|
||
|
||
// Start mapping across all primary owners.
|
||
if err := e.mapper(ctx, cancel, ch, nodes, index, shards, c, opt, mapFn, reduceFn); err != nil {
|
||
return nil, errors.Wrap(err, "starting mapper")
|
||
}
|
||
|
||
// Iterate over all map responses and reduce.
|
||
var result interface{}
|
||
var shardN int
|
||
for {
|
||
select {
|
||
case <-ctx.Done():
|
||
return nil, errors.Wrap(ctx.Err(), "context done")
|
||
case resp := <-ch:
|
||
// On error retry against remaining nodes. If an error returns then
|
||
// the context will cancel and cause all open goroutines to return.
|
||
|
||
if resp.err != nil {
|
||
// Filter out unavailable nodes.
|
||
nodes = Nodes(nodes).Filter(resp.node)
|
||
|
||
// Begin mapper against secondary nodes.
|
||
if err := e.mapper(ctx, cancel, ch, nodes, index, resp.shards, c, opt, mapFn, reduceFn); errors.Cause(err) == errShardUnavailable {
|
||
return nil, resp.err
|
||
} else if err != nil {
|
||
return nil, errors.Wrap(err, "calling mapper")
|
||
}
|
||
continue
|
||
}
|
||
|
||
// Reduce value.
|
||
result = reduceFn(ctx, result, resp.result)
|
||
if err, ok := result.(error); ok {
|
||
cancel()
|
||
return nil, err
|
||
}
|
||
|
||
// If all shards have been processed then return.
|
||
shardN += len(resp.shards)
|
||
if shardN >= len(shards) {
|
||
return result, nil
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// makeEmbeddedDataForShards produces new rows containing the rowSegments
|
||
// that would correspond to a given set of shards.
|
||
func makeEmbeddedDataForShards(allRows []*Row, shards []uint64) []*Row {
|
||
if len(allRows) == 0 || len(shards) == 0 {
|
||
return nil
|
||
}
|
||
newRows := make([]*Row, len(allRows))
|
||
for i, row := range allRows {
|
||
if row == nil || len(row.segments) == 0 {
|
||
continue
|
||
}
|
||
segments := row.segments
|
||
segmentIndex := 0
|
||
newRows[i] = &Row{}
|
||
for _, shard := range shards {
|
||
for segmentIndex < len(segments) && segments[segmentIndex].shard < shard {
|
||
segmentIndex++
|
||
}
|
||
// no more segments in this row
|
||
if segmentIndex >= len(segments) {
|
||
break
|
||
}
|
||
if segments[segmentIndex].shard == shard {
|
||
newRows[i].segments = append(newRows[i].segments, segments[segmentIndex])
|
||
segmentIndex++
|
||
if segmentIndex >= len(segments) {
|
||
// no more segments, we're done
|
||
break
|
||
}
|
||
}
|
||
// if we got here, segments[segmentIndex].shard exists
|
||
// but is greater than the current shard, so we continue.
|
||
}
|
||
}
|
||
return newRows
|
||
}
|
||
|
||
func (e *executor) mapper(ctx context.Context, cancel context.CancelFunc, ch chan mapResponse, nodes []*Node, index string, shards []uint64, c *pql.Call, opt *execOptions, mapFn mapFunc, reduceFn reduceFunc) error {
|
||
span, ctx := tracing.StartSpanFromContext(ctx, "Executor.mapper")
|
||
defer span.Finish()
|
||
done := ctx.Done()
|
||
|
||
// Group shards together by nodes.
|
||
m, err := e.shardsByNode(nodes, index, shards)
|
||
if err != nil {
|
||
return errors.Wrap(err, "shards by node")
|
||
}
|
||
|
||
// Execute each node in a separate goroutine.
|
||
for n, nodeShards := range m {
|
||
go func(n *Node, nodeShards []uint64) {
|
||
resp := mapResponse{node: n, shards: nodeShards}
|
||
|
||
// Send local shards to mapper, otherwise remote exec.
|
||
if n.ID == e.Node.ID {
|
||
resp.result, resp.err = e.mapperLocal(ctx, nodeShards, mapFn, reduceFn)
|
||
} else if !opt.Remote {
|
||
var embeddedRowsForNode []*Row
|
||
if opt.EmbeddedData != nil {
|
||
embeddedRowsForNode = makeEmbeddedDataForShards(opt.EmbeddedData, nodeShards)
|
||
}
|
||
results, err := e.remoteExec(ctx, n, index, &pql.Query{Calls: []*pql.Call{c}}, nodeShards, embeddedRowsForNode)
|
||
if len(results) > 0 {
|
||
resp.result = results[0]
|
||
}
|
||
resp.err = err
|
||
}
|
||
// Return response to the channel.
|
||
select {
|
||
case <-done:
|
||
case ch <- resp:
|
||
// The cancel coming after the above send is intentional.
|
||
// We want to report the actual error that happened
|
||
// before we cause anything to return "context canceled".
|
||
if resp.err != nil {
|
||
cancel()
|
||
}
|
||
}
|
||
}(n, nodeShards)
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
type job struct {
|
||
shard uint64
|
||
mapFn mapFunc
|
||
ctx context.Context
|
||
resultChan chan mapResponse
|
||
}
|
||
|
||
func worker(work chan job) {
|
||
for j := range work {
|
||
result, err := j.mapFn(j.ctx, j.shard)
|
||
|
||
select {
|
||
case <-j.ctx.Done():
|
||
case j.resultChan <- mapResponse{result: result, err: err}:
|
||
}
|
||
}
|
||
}
|
||
|
||
var errShutdown = errors.New("executor has shut down")
|
||
|
||
// mapperLocal performs map & reduce entirely on the local node.
|
||
func (e *executor) mapperLocal(ctx context.Context, shards []uint64, mapFn mapFunc, reduceFn reduceFunc) (_ interface{}, err error) {
|
||
span, ctx := tracing.StartSpanFromContext(ctx, "Executor.mapperLocal")
|
||
defer span.Finish()
|
||
ctx, cancel := context.WithCancel(ctx)
|
||
defer cancel()
|
||
done := ctx.Done()
|
||
e.workMu.RLock()
|
||
defer e.workMu.RUnlock()
|
||
|
||
if e.shutdown {
|
||
return nil, errShutdown
|
||
}
|
||
|
||
ch := make(chan mapResponse, len(shards))
|
||
|
||
for _, shard := range shards {
|
||
e.work <- job{
|
||
shard: shard,
|
||
mapFn: mapFn,
|
||
ctx: ctx,
|
||
resultChan: ch,
|
||
}
|
||
}
|
||
|
||
// Reduce results
|
||
var maxShard int
|
||
var result interface{}
|
||
for {
|
||
select {
|
||
case <-done:
|
||
return nil, ctx.Err()
|
||
case resp := <-ch:
|
||
if resp.err != nil {
|
||
return nil, resp.err
|
||
}
|
||
result = reduceFn(ctx, result, resp.result)
|
||
if err, ok := result.(error); ok {
|
||
cancel()
|
||
return nil, err
|
||
}
|
||
maxShard++
|
||
}
|
||
|
||
// Exit once all shards are processed.
|
||
if maxShard == len(shards) {
|
||
return result, nil
|
||
}
|
||
}
|
||
}
|
||
|
||
func (e *executor) translateCalls(ctx context.Context, defaultIndexName string, calls []*pql.Call) (err error) {
|
||
span, _ := tracing.StartSpanFromContext(ctx, "Executor.translateCalls")
|
||
defer span.Finish()
|
||
|
||
// Generate a list of all used
|
||
keySets := make(map[string]map[string]struct{})
|
||
writable := false
|
||
for _, c := range calls {
|
||
if c.Writable() {
|
||
writable = true
|
||
}
|
||
if err := e.collectCallKeySets(ctx, defaultIndexName, c, keySets); err != nil {
|
||
return err
|
||
}
|
||
}
|
||
|
||
// Perform a separate batch translation for each separate index used.
|
||
keyMaps := make(map[string]map[string]uint64)
|
||
for indexName, keySet := range keySets {
|
||
idx := e.Holder.indexes[indexName]
|
||
if idx == nil {
|
||
return fmt.Errorf("cannot find index %q", indexName)
|
||
}
|
||
|
||
if !idx.Keys() || len(keySets) == 0 {
|
||
continue
|
||
}
|
||
if keyMaps[indexName], err = e.Cluster.translateIndexKeySet(ctx, indexName, keySet, writable); err != nil {
|
||
return err
|
||
}
|
||
}
|
||
|
||
// Translate calls.
|
||
for _, c := range calls {
|
||
if err := e.translateCall(ctx, defaultIndexName, c, keyMaps, c.Writable()); err != nil {
|
||
return err
|
||
}
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
func (e *executor) collectCallKeySets(ctx context.Context, indexName string, c *pql.Call, m map[string]map[string]struct{}) error {
|
||
// Specifying an 'index' call overrides indexes on subsequent calls.
|
||
if s := c.CallIndex(); s != "" {
|
||
indexName = s
|
||
}
|
||
|
||
if m[indexName] == nil {
|
||
m[indexName] = make(map[string]struct{})
|
||
}
|
||
|
||
// Collect key for this call.
|
||
colKey, rowKey, fieldName := c.TranslateInfo(columnLabel, rowLabel)
|
||
if c.Args[colKey] != nil && isString(c.Args[colKey]) {
|
||
if value := callArgString(c, colKey); value != "" {
|
||
m[indexName][value] = struct{}{}
|
||
}
|
||
}
|
||
|
||
// Collect foreign index keys.
|
||
if fieldName != "" {
|
||
idx, exists := e.Holder.indexes[indexName]
|
||
if !exists {
|
||
return newNotFoundError(ErrIndexNotFound, indexName)
|
||
}
|
||
if field := idx.Field(fieldName); field != nil && field.ForeignIndex() != "" {
|
||
foreignIndexName := field.ForeignIndex()
|
||
if m[foreignIndexName] == nil {
|
||
m[foreignIndexName] = make(map[string]struct{})
|
||
}
|
||
|
||
if c.Args[rowKey] != nil && isCondition(c.Args[rowKey]) {
|
||
cond := c.Args[rowKey].(*pql.Condition)
|
||
if isString(cond.Value) {
|
||
m[foreignIndexName][cond.Value.(string)] = struct{}{}
|
||
}
|
||
} else if value := callArgString(c, rowKey); value != "" {
|
||
m[foreignIndexName][value] = struct{}{}
|
||
}
|
||
}
|
||
}
|
||
|
||
// Recursively collect argument calls.
|
||
for _, arg := range c.Args {
|
||
if arg, ok := arg.(*pql.Call); ok {
|
||
if err := e.collectCallKeySets(ctx, indexName, arg, m); err != nil {
|
||
return errors.Wrap(err, "collecting group by call index name")
|
||
}
|
||
}
|
||
}
|
||
|
||
// Recursively collect child calls.
|
||
for _, child := range c.Children {
|
||
if err := e.collectCallKeySets(ctx, indexName, child, m); err != nil {
|
||
return err
|
||
}
|
||
}
|
||
return nil
|
||
}
|
||
|
||
func (e *executor) translateCall(ctx context.Context, indexName string, c *pql.Call, keyMaps map[string]map[string]uint64, writable bool) (err error) {
|
||
// Specifying an 'index' arg applies to all nested calls.
|
||
if s := c.CallIndex(); s != "" {
|
||
indexName = s
|
||
}
|
||
keyMap := keyMaps[indexName]
|
||
|
||
// Translate column key.
|
||
colKey, rowKey, fieldName := c.TranslateInfo(columnLabel, rowLabel)
|
||
idx, exists := e.Holder.indexes[indexName]
|
||
if !exists {
|
||
return newNotFoundError(ErrIndexNotFound, indexName)
|
||
}
|
||
if idx.Keys() {
|
||
if c.Args[colKey] != nil && !isString(c.Args[colKey]) {
|
||
if !isValidID(c.Args[colKey]) {
|
||
return errors.Errorf("column value must be a string or non-negative integer, but got: %v of %[1]T", c.Args[colKey])
|
||
}
|
||
} else if value := callArgString(c, colKey); value != "" {
|
||
c.Args[colKey] = keyMap[value]
|
||
}
|
||
} else {
|
||
if isString(c.Args[colKey]) {
|
||
return errors.New("string 'col' value not allowed unless index 'keys' option enabled")
|
||
}
|
||
}
|
||
|
||
// Translate row key, if field is specified & key exists.
|
||
if fieldName != "" {
|
||
field := idx.Field(fieldName)
|
||
if field == nil {
|
||
// Instead of returning ErrFieldNotFound here,
|
||
// we just return, and don't attempt the translation.
|
||
// The assumption is that the non-existent field
|
||
// will raise an error downstream when it's used.
|
||
return nil
|
||
}
|
||
|
||
// Bool field keys do not use the translator because there
|
||
// are only two possible values. Instead, they are handled
|
||
// directly.
|
||
if field.Type() == FieldTypeBool {
|
||
if c.Name == "Rows" {
|
||
// TranslateInfo for Rows returns "previous" as rowKey,
|
||
// so for bool fields we would get "missing bool argument" error
|
||
return nil
|
||
}
|
||
boolVal, err := callArgBool(c, rowKey)
|
||
if err != nil {
|
||
return errors.Wrapf(err, "getting bool key (%+v)", rowKey)
|
||
}
|
||
rowID := falseRowID
|
||
if boolVal {
|
||
rowID = trueRowID
|
||
}
|
||
c.Args[rowKey] = rowID
|
||
} else if field.Keys() {
|
||
foreignIndexName := field.ForeignIndex()
|
||
if c.Args[rowKey] != nil && isCondition(c.Args[rowKey]) {
|
||
// In the case where a field has a foreign index with keys,
|
||
// allow `== "key"` or `!= "key"` to be used against the BSI
|
||
// field.
|
||
cond := c.Args[rowKey].(*pql.Condition)
|
||
if isString(cond.Value) {
|
||
switch cond.Op {
|
||
case pql.EQ, pql.NEQ:
|
||
var id uint64
|
||
if foreignIndexName != "" {
|
||
id = keyMaps[foreignIndexName][cond.Value.(string)]
|
||
} else {
|
||
if id, err = e.Cluster.translateFieldKey(ctx, field, cond.Value.(string), writable); err != nil {
|
||
return errors.Wrapf(err, "translating field key: %s", cond.Value)
|
||
}
|
||
}
|
||
|
||
c.Args[rowKey] = &pql.Condition{
|
||
Op: cond.Op,
|
||
Value: id,
|
||
}
|
||
default:
|
||
return errors.Errorf("conditional is not supported with string predicates: %s", cond.Op)
|
||
}
|
||
}
|
||
} else if c.Args[rowKey] != nil && !isString(c.Args[rowKey]) {
|
||
// allow passing row id directly (this can come in handy, but make sure it is a valid row id)
|
||
if !isValidID(c.Args[rowKey]) {
|
||
return errors.Errorf("row value must be a string or non-negative integer, but got: %v of %[1]T", c.Args[rowKey])
|
||
}
|
||
} else if value := callArgString(c, rowKey); value != "" {
|
||
var id uint64
|
||
if foreignIndexName != "" {
|
||
id = keyMaps[foreignIndexName][value]
|
||
} else {
|
||
if id, err = e.Cluster.translateFieldKey(ctx, field, value, writable); err != nil {
|
||
return errors.Wrapf(err, "translating field key: %s", value)
|
||
}
|
||
}
|
||
c.Args[rowKey] = id
|
||
}
|
||
} else {
|
||
if isString(c.Args[rowKey]) {
|
||
return errors.New("string 'row' value not allowed unless field 'keys' option enabled")
|
||
}
|
||
}
|
||
}
|
||
|
||
// Translate child calls.
|
||
for _, child := range c.Children {
|
||
if err := e.translateCall(ctx, indexName, child, keyMaps, writable); err != nil {
|
||
return err
|
||
}
|
||
}
|
||
|
||
// Translate call args.
|
||
for _, arg := range c.Args {
|
||
if arg, ok := arg.(*pql.Call); ok {
|
||
if err := e.translateCall(ctx, indexName, arg, keyMaps, writable); err != nil {
|
||
return errors.Wrap(err, "translating arg")
|
||
}
|
||
}
|
||
}
|
||
|
||
// GroupBy-specific call translation.
|
||
if c.Name == "GroupBy" {
|
||
prev, ok := c.Args["previous"]
|
||
if !ok {
|
||
return nil // nothing else to be translated
|
||
}
|
||
previous, ok := prev.([]interface{})
|
||
if !ok {
|
||
return errors.Errorf("'previous' argument must be list, but got %T", prev)
|
||
}
|
||
if len(c.Children) != len(previous) {
|
||
return errors.Errorf("mismatched lengths for previous: %d and children: %d in %s", len(previous), len(c.Children), c)
|
||
}
|
||
|
||
fields := make([]*Field, len(c.Children))
|
||
for i, child := range c.Children {
|
||
var fieldname string
|
||
if fieldname = callArgString(child, "_field"); fieldname == "" {
|
||
// TODO: it's unsettling that we expect "_field" but in some
|
||
// cases get "field". We should figure out why that happens.
|
||
fieldname = callArgString(child, "field")
|
||
}
|
||
field := idx.Field(fieldname)
|
||
if field == nil {
|
||
return errors.Wrapf(ErrFieldNotFound, "getting field '%s' from '%s'", fieldname, child)
|
||
}
|
||
fields[i] = field
|
||
}
|
||
|
||
for i, field := range fields {
|
||
prev := previous[i]
|
||
if field.Keys() {
|
||
prevStr, ok := prev.(string)
|
||
if !ok {
|
||
return errors.New("prev value must be a string when field 'keys' option enabled")
|
||
}
|
||
var id uint64
|
||
var err error
|
||
if fi := field.ForeignIndex(); fi != "" {
|
||
ids, err := e.Cluster.translateIndexKeys(ctx, fi, []string{prevStr}, writable)
|
||
if err != nil {
|
||
return errors.Wrap(err, "translating foreign index key in groupby previous")
|
||
}
|
||
if len(ids) == 1 {
|
||
id = ids[0]
|
||
}
|
||
} else {
|
||
id, err = e.Cluster.translateFieldKey(ctx, field, prevStr, writable)
|
||
if err != nil {
|
||
return errors.Wrapf(err, "translating field key: %s", prevStr)
|
||
}
|
||
}
|
||
previous[i] = id
|
||
} else {
|
||
if prevStr, ok := prev.(string); ok {
|
||
return errors.Errorf("got string row val '%s' in 'previous' for field %s which doesn't use string keys", prevStr, field.Name())
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
func (e *executor) translateResults(ctx context.Context, index string, idx *Index, calls []*pql.Call, results []interface{}) (err error) {
|
||
span, _ := tracing.StartSpanFromContext(ctx, "Executor.translateResults")
|
||
defer span.Finish()
|
||
|
||
idMap := make(map[uint64]string)
|
||
if idx.Keys() {
|
||
// Collect all index ids.
|
||
idSet := make(map[uint64]struct{})
|
||
for i := range calls {
|
||
if err := e.collectResultIDs(index, idx, calls[i], results[i], idSet); err != nil {
|
||
return err
|
||
}
|
||
}
|
||
if idMap, err = e.Cluster.translateIndexIDSet(ctx, index, idSet); err != nil {
|
||
return err
|
||
}
|
||
}
|
||
|
||
for i := range results {
|
||
results[i], err = e.translateResult(ctx, index, idx, calls[i], results[i], idMap)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
}
|
||
return nil
|
||
}
|
||
|
||
func (e *executor) collectResultIDs(index string, idx *Index, call *pql.Call, result interface{}, idSet map[uint64]struct{}) error {
|
||
switch result := result.(type) {
|
||
case *Row:
|
||
if !idx.Keys() {
|
||
return nil
|
||
}
|
||
|
||
for _, segment := range result.Segments() {
|
||
for _, col := range segment.Columns() {
|
||
idSet[col] = struct{}{}
|
||
}
|
||
}
|
||
|
||
case ExtractedIDMatrix:
|
||
for _, col := range result.Columns {
|
||
idSet[col.ColumnID] = struct{}{}
|
||
}
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
func (e *executor) translateFieldIDs(field *Field, ids map[uint64]struct{}) (map[uint64]string, error) {
|
||
idList := make([]uint64, len(ids))
|
||
{
|
||
i := 0
|
||
for id := range ids {
|
||
idList[i] = id
|
||
i++
|
||
}
|
||
}
|
||
keyList, err := field.TranslateStore().TranslateIDs(idList)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
mapped := make(map[uint64]string, len(idList))
|
||
for i, key := range keyList {
|
||
mapped[idList[i]] = key
|
||
}
|
||
return mapped, nil
|
||
}
|
||
|
||
// preTranslateMatrixSet translates the IDs of a set field in an extracted matrix.
|
||
func (e *executor) preTranslateMatrixSet(mat ExtractedIDMatrix, fieldIdx uint, field *Field) (map[uint64]string, error) {
|
||
ids := make(map[uint64]struct{}, len(mat.Columns))
|
||
for _, col := range mat.Columns {
|
||
for _, v := range col.Rows[fieldIdx] {
|
||
ids[v] = struct{}{}
|
||
}
|
||
}
|
||
|
||
return e.translateFieldIDs(field, ids)
|
||
}
|
||
|
||
func (e *executor) translateResult(ctx context.Context, index string, idx *Index, call *pql.Call, result interface{}, idSet map[uint64]string) (_ interface{}, err error) {
|
||
|
||
switch result := result.(type) {
|
||
case *Row:
|
||
if idx.Keys() {
|
||
other := &Row{Attrs: result.Attrs}
|
||
for _, segment := range result.Segments() {
|
||
for _, col := range segment.Columns() {
|
||
other.Keys = append(other.Keys, idSet[col])
|
||
}
|
||
}
|
||
return other, nil
|
||
}
|
||
|
||
// TODO: instead of supporting SignedRow here, we may be able to
|
||
// make the return type for an int field with a ForeignIndex be
|
||
// a *Row instead (because it should always be positive).
|
||
case SignedRow:
|
||
sr, err := func() (*SignedRow, error) {
|
||
fieldName := callArgString(call, "field")
|
||
if fieldName == "" {
|
||
return nil, nil
|
||
}
|
||
|
||
field := idx.Field(fieldName)
|
||
if field == nil {
|
||
return nil, nil
|
||
}
|
||
|
||
if field.Keys() {
|
||
rslt := result.Pos
|
||
other := &Row{Attrs: rslt.Attrs}
|
||
for _, segment := range rslt.Segments() {
|
||
keys, err := e.Cluster.translateIndexIDs(context.Background(), field.ForeignIndex(), segment.Columns())
|
||
if err != nil {
|
||
return nil, errors.Wrap(err, "translating index ids")
|
||
}
|
||
other.Keys = append(other.Keys, keys...)
|
||
}
|
||
return &SignedRow{Pos: other}, nil
|
||
}
|
||
|
||
return nil, nil
|
||
}()
|
||
if err != nil {
|
||
return nil, err
|
||
} else if sr != nil {
|
||
return *sr, nil
|
||
}
|
||
|
||
case PairField:
|
||
if fieldName := callArgString(call, "field"); fieldName != "" {
|
||
field := idx.Field(fieldName)
|
||
if field == nil {
|
||
return nil, fmt.Errorf("field %q not found", fieldName)
|
||
}
|
||
if field.Keys() {
|
||
key, err := field.TranslateStore().TranslateID(result.Pair.ID)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
if call.Name == "MinRow" || call.Name == "MaxRow" {
|
||
result.Pair.Key = key
|
||
return result, nil
|
||
}
|
||
return PairField{
|
||
Pair: Pair{Key: key, Count: result.Pair.Count},
|
||
Field: fieldName,
|
||
}, nil
|
||
}
|
||
}
|
||
|
||
case *PairsField:
|
||
if fieldName := callArgString(call, "_field"); fieldName != "" {
|
||
field := idx.Field(fieldName)
|
||
if field == nil {
|
||
return nil, fmt.Errorf("field %q not found", fieldName)
|
||
}
|
||
if field.Keys() {
|
||
ids := make([]uint64, len(result.Pairs))
|
||
for i := range result.Pairs {
|
||
ids[i] = result.Pairs[i].ID
|
||
}
|
||
keys, err := field.TranslateStore().TranslateIDs(ids)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
other := make([]Pair, len(result.Pairs))
|
||
for i := range result.Pairs {
|
||
other[i] = Pair{Key: keys[i], Count: result.Pairs[i].Count}
|
||
}
|
||
return &PairsField{
|
||
Pairs: other,
|
||
Field: fieldName,
|
||
}, nil
|
||
}
|
||
}
|
||
|
||
case []GroupCount:
|
||
fieldIDs := make(map[*Field]map[uint64]struct{})
|
||
foreignIDs := make(map[*Field]map[uint64]struct{})
|
||
for _, gl := range result {
|
||
for _, g := range gl.Group {
|
||
field := idx.Field(g.Field)
|
||
if field == nil {
|
||
return nil, newNotFoundError(ErrFieldNotFound, g.Field)
|
||
}
|
||
if field.Keys() {
|
||
if g.Value != nil {
|
||
if fi := field.ForeignIndex(); fi != "" {
|
||
m, ok := foreignIDs[field]
|
||
if !ok {
|
||
m = make(map[uint64]struct{}, len(result))
|
||
foreignIDs[field] = m
|
||
}
|
||
|
||
m[uint64(*g.Value)] = struct{}{}
|
||
continue
|
||
}
|
||
}
|
||
|
||
m, ok := fieldIDs[field]
|
||
if !ok {
|
||
m = make(map[uint64]struct{}, len(result))
|
||
fieldIDs[field] = m
|
||
}
|
||
|
||
m[g.RowID] = struct{}{}
|
||
}
|
||
}
|
||
}
|
||
|
||
fieldTranslations := make(map[string]map[uint64]string)
|
||
for field, ids := range fieldIDs {
|
||
trans, err := e.translateFieldIDs(field, ids)
|
||
if err != nil {
|
||
return nil, errors.Wrapf(err, "translating IDs in field %q", field.Name())
|
||
}
|
||
fieldTranslations[field.Name()] = trans
|
||
}
|
||
|
||
foreignTranslations := make(map[string]map[uint64]string)
|
||
for field, ids := range foreignIDs {
|
||
trans, err := e.Cluster.translateIndexIDSet(ctx, field.ForeignIndex(), ids)
|
||
if err != nil {
|
||
return nil, errors.Wrapf(err, "translating foreign IDs from index %q", field.ForeignIndex())
|
||
}
|
||
foreignTranslations[field.Name()] = trans
|
||
}
|
||
|
||
other := make([]GroupCount, 0)
|
||
for _, gl := range result {
|
||
|
||
group := make([]FieldRow, len(gl.Group))
|
||
for i, g := range gl.Group {
|
||
if ft, ok := fieldTranslations[g.Field]; ok {
|
||
g.RowKey = ft[g.RowID]
|
||
} else if ft, ok := foreignTranslations[g.Field]; ok && g.Value != nil {
|
||
g.RowKey = ft[uint64(*g.Value)]
|
||
g.Value = nil
|
||
}
|
||
|
||
group[i] = g
|
||
}
|
||
|
||
other = append(other, GroupCount{
|
||
Group: group,
|
||
Count: gl.Count,
|
||
Sum: gl.Sum,
|
||
})
|
||
}
|
||
return other, nil
|
||
|
||
case RowIDs:
|
||
fieldName := callArgString(call, "_field")
|
||
if fieldName == "" {
|
||
return nil, ErrFieldNotFound
|
||
}
|
||
|
||
other := RowIdentifiers{
|
||
field: fieldName,
|
||
}
|
||
|
||
if field := idx.Field(fieldName); field == nil {
|
||
return nil, newNotFoundError(ErrFieldNotFound, fieldName)
|
||
} else if field.Keys() {
|
||
keys, err := field.TranslateStore().TranslateIDs(result)
|
||
if err != nil {
|
||
return nil, errors.Wrap(err, "translating row IDs")
|
||
}
|
||
other.Keys = keys
|
||
} else {
|
||
other.Rows = result
|
||
}
|
||
|
||
return other, nil
|
||
|
||
case ExtractedIDMatrix:
|
||
type fieldMapper = func([]uint64) (_ interface{}, err error)
|
||
|
||
fields := make([]ExtractedTableField, len(result.Fields))
|
||
mappers := make([]fieldMapper, len(result.Fields))
|
||
for i, v := range result.Fields {
|
||
field := idx.Field(v)
|
||
if field == nil {
|
||
return nil, newNotFoundError(ErrFieldNotFound, v)
|
||
}
|
||
|
||
datatype, err := field.Datatype()
|
||
if err != nil {
|
||
return nil, errors.Wrapf(err, "field %s", v)
|
||
}
|
||
fields[i] = ExtractedTableField{
|
||
Name: v,
|
||
Type: datatype,
|
||
}
|
||
|
||
var mapper fieldMapper
|
||
switch typ := field.Type(); typ {
|
||
case FieldTypeBool:
|
||
mapper = func(ids []uint64) (_ interface{}, err error) {
|
||
switch len(ids) {
|
||
case 0:
|
||
return nil, nil
|
||
case 1:
|
||
switch ids[0] {
|
||
case 0:
|
||
return false, nil
|
||
case 1:
|
||
return true, nil
|
||
default:
|
||
return nil, errors.Errorf("invalid ID for boolean %q: %d", field.Name(), ids[0])
|
||
}
|
||
default:
|
||
return nil, errors.Errorf("boolean %q has too many values: %v", field.Name(), ids)
|
||
}
|
||
}
|
||
case FieldTypeSet, FieldTypeTime:
|
||
if field.Keys() {
|
||
translations, err := e.preTranslateMatrixSet(result, uint(i), field)
|
||
if err != nil {
|
||
return nil, errors.Wrapf(err, "translating IDs of field %q", v)
|
||
}
|
||
mapper = func(ids []uint64) (interface{}, error) {
|
||
keys := make([]string, len(ids))
|
||
for i, id := range ids {
|
||
keys[i] = translations[id]
|
||
}
|
||
return keys, nil
|
||
}
|
||
} else {
|
||
mapper = func(ids []uint64) (interface{}, error) {
|
||
if ids == nil {
|
||
ids = []uint64{}
|
||
}
|
||
return ids, nil
|
||
}
|
||
}
|
||
case FieldTypeMutex:
|
||
if field.Keys() {
|
||
translations, err := e.preTranslateMatrixSet(result, uint(i), field)
|
||
if err != nil {
|
||
return nil, errors.Wrapf(err, "translating IDs of field %q", v)
|
||
}
|
||
mapper = func(ids []uint64) (interface{}, error) {
|
||
switch len(ids) {
|
||
case 0:
|
||
return nil, nil
|
||
case 1:
|
||
return translations[ids[0]], nil
|
||
default:
|
||
return nil, errors.Errorf("mutex %q has too many values: %v", field.Name(), ids)
|
||
}
|
||
}
|
||
} else {
|
||
mapper = func(ids []uint64) (_ interface{}, err error) {
|
||
switch len(ids) {
|
||
case 0:
|
||
return nil, nil
|
||
case 1:
|
||
return ids[0], nil
|
||
default:
|
||
return nil, errors.Errorf("mutex %q has too many values: %v", field.Name(), ids)
|
||
}
|
||
}
|
||
}
|
||
case FieldTypeInt:
|
||
if fi := field.ForeignIndex(); fi != "" {
|
||
if field.Keys() {
|
||
ids := make(map[uint64]struct{}, len(result.Columns))
|
||
for _, col := range result.Columns {
|
||
for _, v := range col.Rows[i] {
|
||
ids[v] = struct{}{}
|
||
}
|
||
}
|
||
trans, err := e.Cluster.translateIndexIDSet(ctx, field.ForeignIndex(), ids)
|
||
if err != nil {
|
||
return nil, errors.Wrapf(err, "translating foreign IDs from index %q", field.ForeignIndex())
|
||
}
|
||
mapper = func(ids []uint64) (interface{}, error) {
|
||
switch len(ids) {
|
||
case 0:
|
||
return nil, nil
|
||
case 1:
|
||
return trans[ids[0]], nil
|
||
default:
|
||
return nil, errors.Errorf("BSI field %q has too many values: %v", field.Name(), ids)
|
||
}
|
||
}
|
||
} else {
|
||
mapper = func(ids []uint64) (interface{}, error) {
|
||
switch len(ids) {
|
||
case 0:
|
||
return nil, nil
|
||
case 1:
|
||
return ids[0], nil
|
||
default:
|
||
return nil, errors.Errorf("BSI field %q has too many values: %v", field.Name(), ids)
|
||
}
|
||
}
|
||
}
|
||
} else {
|
||
mapper = func(ids []uint64) (interface{}, error) {
|
||
switch len(ids) {
|
||
case 0:
|
||
return nil, nil
|
||
case 1:
|
||
return int64(ids[0]), nil
|
||
default:
|
||
return nil, errors.Errorf("BSI field %q has too many values: %v", field.Name(), ids)
|
||
}
|
||
}
|
||
}
|
||
case FieldTypeDecimal:
|
||
scale := field.Options().Scale
|
||
mapper = func(ids []uint64) (_ interface{}, err error) {
|
||
switch len(ids) {
|
||
case 0:
|
||
return nil, nil
|
||
case 1:
|
||
return pql.NewDecimal(int64(ids[0]), scale), nil
|
||
default:
|
||
return nil, errors.Errorf("BSI field %q has too many values: %v", field.Name(), ids)
|
||
}
|
||
}
|
||
default:
|
||
return nil, errors.Errorf("field type %q not yet supported", typ)
|
||
}
|
||
mappers[i] = mapper
|
||
}
|
||
|
||
var translateCol func(uint64) (KeyOrID, error)
|
||
if idx.keys {
|
||
translateCol = func(id uint64) (KeyOrID, error) {
|
||
return KeyOrID{Keyed: true, Key: idSet[id]}, nil
|
||
}
|
||
} else {
|
||
translateCol = func(id uint64) (KeyOrID, error) {
|
||
return KeyOrID{ID: id}, nil
|
||
}
|
||
}
|
||
|
||
cols := make([]ExtractedTableColumn, len(result.Columns))
|
||
colData := make([]interface{}, len(cols)*len(result.Fields))
|
||
for i, col := range result.Columns {
|
||
data := colData[i*len(result.Fields) : (i+1)*len(result.Fields) : (i+1)*len(result.Fields)]
|
||
for j, rows := range col.Rows {
|
||
v, err := mappers[j](rows)
|
||
if err != nil {
|
||
return nil, errors.Wrap(err, "translating extracted table value")
|
||
}
|
||
data[j] = v
|
||
}
|
||
|
||
colTrans, err := translateCol(col.ColumnID)
|
||
if err != nil {
|
||
return nil, errors.Wrap(err, "translating column ID in extracted table")
|
||
}
|
||
|
||
cols[i] = ExtractedTableColumn{
|
||
Column: colTrans,
|
||
Rows: data,
|
||
}
|
||
}
|
||
|
||
return ExtractedTable{
|
||
Fields: fields,
|
||
Columns: cols,
|
||
}, nil
|
||
}
|
||
|
||
return result, nil
|
||
}
|
||
|
||
// detectRangeCall returns true if the call or one of its children contains a Range call
|
||
// TODO: Remove at version 2.0
|
||
func (e *executor) detectRangeCall(c *pql.Call) bool {
|
||
// detect whether there is a Range call
|
||
if c.Name == "Range" {
|
||
return true
|
||
}
|
||
for _, c := range c.Children {
|
||
if e.detectRangeCall(c) {
|
||
return true
|
||
}
|
||
}
|
||
return false
|
||
}
|
||
|
||
// validateQueryContext returns a query-appropriate error if the context is done.
|
||
func validateQueryContext(ctx context.Context) error {
|
||
select {
|
||
case <-ctx.Done():
|
||
switch err := ctx.Err(); err {
|
||
case context.Canceled:
|
||
return ErrQueryCancelled
|
||
case context.DeadlineExceeded:
|
||
return ErrQueryTimeout
|
||
default:
|
||
return err
|
||
}
|
||
default:
|
||
return nil
|
||
}
|
||
}
|
||
|
||
// errShardUnavailable is a marker error if no nodes are available.
|
||
var errShardUnavailable = errors.New("shard unavailable")
|
||
|
||
type mapFunc func(ctx context.Context, shard uint64) (_ interface{}, err error)
|
||
|
||
type reduceFunc func(ctx context.Context, prev, v interface{}) interface{}
|
||
|
||
type mapResponse struct {
|
||
node *Node
|
||
shards []uint64
|
||
|
||
result interface{}
|
||
err error
|
||
}
|
||
|
||
// execOptions represents an execution context for a single Execute() call.
|
||
type execOptions struct {
|
||
Remote bool
|
||
Profile bool
|
||
ExcludeRowAttrs bool
|
||
ExcludeColumns bool
|
||
ColumnAttrs bool
|
||
EmbeddedData []*Row
|
||
}
|
||
|
||
// hasOnlySetRowAttrs returns true if calls only contains SetRowAttrs() calls.
|
||
func hasOnlySetRowAttrs(calls []*pql.Call) bool {
|
||
if len(calls) == 0 {
|
||
return false
|
||
}
|
||
|
||
for _, call := range calls {
|
||
if call.Name != "SetRowAttrs" {
|
||
return false
|
||
}
|
||
}
|
||
return true
|
||
}
|
||
|
||
func needsShards(calls []*pql.Call) bool {
|
||
if len(calls) == 0 {
|
||
return false
|
||
}
|
||
for _, call := range calls {
|
||
switch call.Name {
|
||
case "Clear", "Set", "SetRowAttrs", "SetColumnAttrs":
|
||
continue
|
||
case "Count", "TopN", "Rows":
|
||
return true
|
||
// default catches Bitmap calls
|
||
default:
|
||
return true
|
||
}
|
||
}
|
||
return false
|
||
}
|
||
|
||
// SignedRow represents a signed *Row with two (neg/pos) *Rows.
|
||
type SignedRow struct {
|
||
Neg *Row `json:"neg"`
|
||
Pos *Row `json:"pos"`
|
||
field string
|
||
}
|
||
|
||
func (s *SignedRow) Clone() (r *SignedRow) {
|
||
r = &SignedRow{
|
||
Neg: s.Neg.Clone(), // Row.Clone() returns nil for nil.
|
||
Pos: s.Pos.Clone(),
|
||
field: s.field,
|
||
}
|
||
return
|
||
}
|
||
|
||
// Field returns the field name associated to the signed row.
|
||
func (s *SignedRow) Field() string {
|
||
return s.field
|
||
}
|
||
|
||
// ToTable implements the ToTabler interface.
|
||
func (s SignedRow) ToTable() (*pb.TableResponse, error) {
|
||
var n uint64
|
||
if s.Neg != nil {
|
||
n += s.Neg.Count()
|
||
}
|
||
if s.Pos != nil {
|
||
n += s.Pos.Count()
|
||
}
|
||
return pb.RowsToTable(&s, int(n))
|
||
}
|
||
|
||
// ToRows implements the ToRowser interface.
|
||
func (s SignedRow) ToRows(callback func(*pb.RowResponse) error) error {
|
||
|
||
ci := []*pb.ColumnInfo{{Name: s.Field(), Datatype: "int64"}}
|
||
negs := s.Neg.Columns()
|
||
for i := len(negs) - 1; i >= 0; i-- {
|
||
val, err := toNegInt64(negs[i])
|
||
if err != nil {
|
||
return errors.Wrap(err, "converting uint64 to int64 (negative)")
|
||
}
|
||
|
||
if err := callback(&pb.RowResponse{
|
||
Headers: ci,
|
||
Columns: []*pb.ColumnResponse{
|
||
&pb.ColumnResponse{ColumnVal: &pb.ColumnResponse_Int64Val{Int64Val: val}},
|
||
},
|
||
}); err != nil {
|
||
return errors.Wrap(err, "calling callback")
|
||
}
|
||
ci = nil
|
||
}
|
||
for _, id := range s.Pos.Columns() {
|
||
val, err := toInt64(id)
|
||
if err != nil {
|
||
return errors.Wrap(err, "converting uint64 to int64 (positive)")
|
||
}
|
||
|
||
if err := callback(&pb.RowResponse{
|
||
Headers: ci,
|
||
Columns: []*pb.ColumnResponse{
|
||
&pb.ColumnResponse{ColumnVal: &pb.ColumnResponse_Int64Val{Int64Val: val}},
|
||
},
|
||
}); err != nil {
|
||
return errors.Wrap(err, "calling callback")
|
||
}
|
||
ci = nil
|
||
}
|
||
return nil
|
||
}
|
||
|
||
func toNegInt64(n uint64) (int64, error) {
|
||
const absMinInt64 = uint64(1 << 63)
|
||
|
||
if n > absMinInt64 {
|
||
return 0, errors.Errorf("value %d overflows int64", n)
|
||
}
|
||
|
||
if n == absMinInt64 {
|
||
return int64(-1 << 63), nil
|
||
}
|
||
|
||
// n < 1 << 63
|
||
return -int64(n), nil
|
||
}
|
||
|
||
func toInt64(n uint64) (int64, error) {
|
||
const maxInt64 = uint64(1<<63) - 1
|
||
|
||
if n > maxInt64 {
|
||
return 0, errors.Errorf("value %d overflows int64", n)
|
||
}
|
||
|
||
return int64(n), nil
|
||
}
|
||
|
||
func (sr *SignedRow) union(other SignedRow) SignedRow {
|
||
ret := SignedRow{&Row{}, &Row{}, ""}
|
||
|
||
// merge in sr
|
||
if sr != nil {
|
||
if sr.Neg != nil {
|
||
ret.Neg = ret.Neg.Union(sr.Neg)
|
||
}
|
||
if sr.Pos != nil {
|
||
ret.Pos = ret.Pos.Union(sr.Pos)
|
||
}
|
||
}
|
||
|
||
// merge in other
|
||
if other.Neg != nil {
|
||
ret.Neg = ret.Neg.Union(other.Neg)
|
||
}
|
||
if other.Pos != nil {
|
||
ret.Pos = ret.Pos.Union(other.Pos)
|
||
}
|
||
|
||
return ret
|
||
}
|
||
|
||
// ValCount represents a grouping of sum & count for Sum() and Average() calls. Also Min, Max....
|
||
type ValCount struct {
|
||
Val int64 `json:"value"`
|
||
FloatVal float64 `json:"floatValue"`
|
||
DecimalVal *pql.Decimal `json:"decimalValue"`
|
||
Count int64 `json:"count"`
|
||
}
|
||
|
||
func (v *ValCount) Clone() (r *ValCount) {
|
||
r = &ValCount{
|
||
Val: v.Val,
|
||
FloatVal: v.FloatVal,
|
||
Count: v.Count,
|
||
}
|
||
if v.DecimalVal != nil {
|
||
r.DecimalVal = v.DecimalVal.Clone()
|
||
}
|
||
return
|
||
}
|
||
|
||
// ToTable implements the ToTabler interface.
|
||
func (v ValCount) ToTable() (*pb.TableResponse, error) {
|
||
return pb.RowsToTable(&v, 1)
|
||
}
|
||
|
||
// ToRows implements the ToRowser interface.
|
||
func (v ValCount) ToRows(callback func(*pb.RowResponse) error) error {
|
||
var ci []*pb.ColumnInfo
|
||
// ValCount can have a decimal, float, or integer value, but
|
||
// not more than one (as of this writing).
|
||
if v.DecimalVal != nil {
|
||
ci = []*pb.ColumnInfo{
|
||
{Name: "value", Datatype: "decimal"},
|
||
{Name: "count", Datatype: "int64"},
|
||
}
|
||
if err := callback(&pb.RowResponse{
|
||
Headers: ci,
|
||
Columns: []*pb.ColumnResponse{
|
||
&pb.ColumnResponse{ColumnVal: &pb.ColumnResponse_DecimalVal{DecimalVal: &pb.Decimal{Value: v.DecimalVal.Value, Scale: v.DecimalVal.Scale}}},
|
||
&pb.ColumnResponse{ColumnVal: &pb.ColumnResponse_Int64Val{Int64Val: v.Count}},
|
||
}}); err != nil {
|
||
return errors.Wrap(err, "calling callback")
|
||
}
|
||
} else if v.FloatVal != 0 {
|
||
ci = []*pb.ColumnInfo{
|
||
{Name: "value", Datatype: "float64"},
|
||
{Name: "count", Datatype: "int64"},
|
||
}
|
||
if err := callback(&pb.RowResponse{
|
||
Headers: ci,
|
||
Columns: []*pb.ColumnResponse{
|
||
&pb.ColumnResponse{ColumnVal: &pb.ColumnResponse_Float64Val{Float64Val: v.FloatVal}},
|
||
&pb.ColumnResponse{ColumnVal: &pb.ColumnResponse_Int64Val{Int64Val: v.Count}},
|
||
}}); err != nil {
|
||
return errors.Wrap(err, "calling callback")
|
||
}
|
||
} else {
|
||
ci = []*pb.ColumnInfo{
|
||
{Name: "value", Datatype: "int64"},
|
||
{Name: "count", Datatype: "int64"},
|
||
}
|
||
if err := callback(&pb.RowResponse{
|
||
Headers: ci,
|
||
Columns: []*pb.ColumnResponse{
|
||
&pb.ColumnResponse{ColumnVal: &pb.ColumnResponse_Int64Val{Int64Val: v.Val}},
|
||
&pb.ColumnResponse{ColumnVal: &pb.ColumnResponse_Int64Val{Int64Val: v.Count}},
|
||
}}); err != nil {
|
||
return errors.Wrap(err, "calling callback")
|
||
}
|
||
}
|
||
return nil
|
||
}
|
||
|
||
func (vc *ValCount) add(other ValCount) ValCount {
|
||
return ValCount{
|
||
Val: vc.Val + other.Val,
|
||
Count: vc.Count + other.Count,
|
||
}
|
||
}
|
||
|
||
// smaller returns the smaller of the two ValCounts.
|
||
func (vc *ValCount) smaller(other ValCount) ValCount {
|
||
if vc.DecimalVal != nil || other.DecimalVal != nil {
|
||
return vc.decimalSmaller(other)
|
||
} else if vc.FloatVal != 0 || other.FloatVal != 0 {
|
||
return vc.floatSmaller(other)
|
||
}
|
||
if vc.Count == 0 || (other.Val < vc.Val && other.Count > 0) {
|
||
return other
|
||
}
|
||
extra := int64(0)
|
||
if vc.Val == other.Val {
|
||
extra += other.Count
|
||
}
|
||
return ValCount{
|
||
Val: vc.Val,
|
||
Count: vc.Count + extra,
|
||
}
|
||
}
|
||
|
||
func (vc *ValCount) decimalSmaller(other ValCount) ValCount {
|
||
if other.DecimalVal == nil {
|
||
return *vc
|
||
}
|
||
if vc.Count == 0 || vc.DecimalVal == nil || (other.DecimalVal.LessThan(*vc.DecimalVal) && other.Count > 0) {
|
||
return other
|
||
}
|
||
extra := int64(0)
|
||
if vc.DecimalVal.EqualTo(*other.DecimalVal) {
|
||
extra += other.Count
|
||
}
|
||
return ValCount{
|
||
DecimalVal: vc.DecimalVal,
|
||
Count: vc.Count + extra,
|
||
}
|
||
}
|
||
|
||
func (vc *ValCount) floatSmaller(other ValCount) ValCount {
|
||
if vc.Count == 0 || (other.FloatVal < vc.FloatVal && other.Count > 0) {
|
||
return other
|
||
}
|
||
extra := int64(0)
|
||
if vc.FloatVal == other.FloatVal {
|
||
extra += other.Count
|
||
}
|
||
return ValCount{
|
||
FloatVal: vc.FloatVal,
|
||
Count: vc.Count + extra,
|
||
}
|
||
}
|
||
|
||
// larger returns the larger of the two ValCounts.
|
||
func (vc *ValCount) larger(other ValCount) ValCount {
|
||
if vc.DecimalVal != nil || other.DecimalVal != nil {
|
||
return vc.decimalLarger(other)
|
||
} else if vc.FloatVal != 0 || other.FloatVal != 0 {
|
||
return vc.floatLarger(other)
|
||
}
|
||
if vc.Count == 0 || (other.Val > vc.Val && other.Count > 0) {
|
||
return other
|
||
}
|
||
extra := int64(0)
|
||
if vc.Val == other.Val {
|
||
extra += other.Count
|
||
}
|
||
return ValCount{
|
||
Val: vc.Val,
|
||
Count: vc.Count + extra,
|
||
}
|
||
}
|
||
|
||
func (vc *ValCount) decimalLarger(other ValCount) ValCount {
|
||
if other.DecimalVal == nil {
|
||
return *vc
|
||
}
|
||
if vc.Count == 0 || vc.DecimalVal == nil || (other.DecimalVal.GreaterThan(*vc.DecimalVal) && other.Count > 0) {
|
||
return other
|
||
}
|
||
extra := int64(0)
|
||
if vc.DecimalVal.EqualTo(*other.DecimalVal) {
|
||
extra += other.Count
|
||
}
|
||
return ValCount{
|
||
DecimalVal: vc.DecimalVal,
|
||
Count: vc.Count + extra,
|
||
}
|
||
}
|
||
|
||
func (vc *ValCount) floatLarger(other ValCount) ValCount {
|
||
if vc.Count == 0 || (other.FloatVal > vc.FloatVal && other.Count > 0) {
|
||
return other
|
||
}
|
||
extra := int64(0)
|
||
if vc.FloatVal == other.FloatVal {
|
||
extra += other.Count
|
||
}
|
||
return ValCount{
|
||
FloatVal: vc.FloatVal,
|
||
Count: vc.Count + extra,
|
||
}
|
||
}
|
||
|
||
func callArgBool(call *pql.Call, key string) (bool, error) {
|
||
value, ok := call.Args[key]
|
||
if !ok {
|
||
return false, errors.New("missing bool argument")
|
||
}
|
||
b, ok := value.(bool)
|
||
if !ok {
|
||
return false, fmt.Errorf("invalid bool argument type: %T", value)
|
||
}
|
||
return b, nil
|
||
}
|
||
|
||
func callArgString(call *pql.Call, key string) string {
|
||
value, ok := call.Args[key]
|
||
if !ok {
|
||
return ""
|
||
}
|
||
s, _ := value.(string)
|
||
return s
|
||
}
|
||
|
||
func isString(v interface{}) bool {
|
||
_, ok := v.(string)
|
||
return ok
|
||
}
|
||
|
||
func isCondition(v interface{}) bool {
|
||
_, ok := v.(*pql.Condition)
|
||
return ok
|
||
}
|
||
|
||
// isValidID returns whether v can be interpreted as a valid row or
|
||
// column ID. In short, is v a non-negative integer? I think the int64
|
||
// and default cases are the only ones actually used since the PQL
|
||
// parser doesn't return any other integer types.
|
||
func isValidID(v interface{}) bool {
|
||
switch vt := v.(type) {
|
||
case uint, uint64, uint32, uint16, uint8:
|
||
return true
|
||
case int64:
|
||
return vt >= 0
|
||
case int:
|
||
return vt >= 0
|
||
case int32:
|
||
return vt >= 0
|
||
case int16:
|
||
return vt >= 0
|
||
case int8:
|
||
return vt >= 0
|
||
default:
|
||
return false
|
||
}
|
||
}
|
||
|
||
// groupByIterator contains several slices. Each slice contains a number of
|
||
// elements equal to the number of fields in the group by (the number of Rows
|
||
// calls).
|
||
type groupByIterator struct {
|
||
executor *executor
|
||
qcx *Qcx
|
||
index string
|
||
shard uint64
|
||
|
||
// rowIters contains a rowIterator for each of the fields in the Group By.
|
||
rowIters []rowIterator
|
||
// rows contains the current row data for each of the fields in the Group
|
||
// By. Each row is the intersection of itself and the rows of the fields
|
||
// with an index lower than its own. This is a performance optimization so
|
||
// that the expected common case of getting the next row in the furthest
|
||
// field to the right require only a single intersect with the row of the
|
||
// previous field to determine the count of the new group.
|
||
rows []struct {
|
||
row *Row
|
||
id uint64
|
||
value *int64
|
||
}
|
||
|
||
// fields helps with the construction of GroupCount results by holding all
|
||
// the field names that are being grouped by. Each results makes a copy of
|
||
// fields and then sets the row ids.
|
||
fields []FieldRow
|
||
done bool
|
||
|
||
// Optional filter row to intersect against first level of values.
|
||
filter *Row
|
||
|
||
// Optional aggregate function to execute for each group.
|
||
aggregate *pql.Call
|
||
}
|
||
|
||
// newGroupByIterator initializes a new groupByIterator.
|
||
func newGroupByIterator(executor *executor, qcx *Qcx, rowIDs []RowIDs, children []*pql.Call, aggregate *pql.Call, filter *Row, index string, shard uint64, holder *Holder) (_ *groupByIterator, err error) {
|
||
|
||
gbi := &groupByIterator{
|
||
executor: executor,
|
||
qcx: qcx,
|
||
index: index,
|
||
shard: shard,
|
||
rowIters: make([]rowIterator, len(children)),
|
||
rows: make([]struct {
|
||
row *Row
|
||
id uint64
|
||
value *int64
|
||
}, len(children)),
|
||
filter: filter,
|
||
aggregate: aggregate,
|
||
fields: make([]FieldRow, len(children)),
|
||
}
|
||
idx := holder.Index(index)
|
||
|
||
var (
|
||
fieldName string
|
||
viewName string
|
||
ok bool
|
||
)
|
||
ignorePrev := false
|
||
for i, call := range children {
|
||
if fieldName, ok = call.Args["_field"].(string); !ok {
|
||
return nil, errors.Errorf("%s call must have field with valid (string) field name. Got %v of type %[2]T", call.Name, call.Args["_field"])
|
||
}
|
||
field := holder.Field(index, fieldName)
|
||
if field == nil {
|
||
return nil, newNotFoundError(ErrFieldNotFound, fieldName)
|
||
}
|
||
gbi.fields[i].Field = fieldName
|
||
|
||
switch field.Type() {
|
||
case FieldTypeSet, FieldTypeTime, FieldTypeMutex, FieldTypeBool:
|
||
viewName = viewStandard
|
||
|
||
case FieldTypeInt:
|
||
viewName = viewBSIGroupPrefix + fieldName
|
||
|
||
default: // FieldTypeDecimal
|
||
return nil, errors.Errorf("%s call must have field of one of types: %s",
|
||
call.Name, strings.Join([]string{FieldTypeSet, FieldTypeTime, FieldTypeMutex, FieldTypeBool, FieldTypeInt}, ","))
|
||
}
|
||
|
||
// Fetch fragment.
|
||
frag := holder.fragment(index, fieldName, viewName, shard)
|
||
if frag == nil { // this means this whole shard doesn't have all it needs to continue
|
||
return nil, nil
|
||
}
|
||
filters := []rowFilter{}
|
||
if len(rowIDs[i]) > 0 {
|
||
filters = append(filters, filterWithRows(rowIDs[i]))
|
||
}
|
||
|
||
tx, finisher := qcx.GetTx(Txo{Write: !writable, Index: idx, Shard: shard})
|
||
defer finisher(&err)
|
||
|
||
gbi.rowIters[i], err = frag.rowIterator(tx, i != 0, filters...)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
prev, hasPrev, err := call.UintArg("previous")
|
||
if err != nil {
|
||
return nil, errors.Wrap(err, "getting previous")
|
||
} else if hasPrev && !ignorePrev {
|
||
if i == len(children)-1 {
|
||
prev++
|
||
}
|
||
gbi.rowIters[i].Seek(prev)
|
||
}
|
||
nextRow, rowID, value, wrapped, err := gbi.rowIters[i].Next()
|
||
if err != nil {
|
||
return nil, err
|
||
} else if nextRow == nil {
|
||
gbi.done = true
|
||
return gbi, nil
|
||
}
|
||
gbi.rows[i].row = nextRow
|
||
gbi.rows[i].id = rowID
|
||
gbi.rows[i].value = value
|
||
if hasPrev && rowID != prev {
|
||
// ignorePrev signals that we didn't find a previous row, so all
|
||
// Rows queries "deeper" than it need to ignore the previous
|
||
// argument and start at the beginning.
|
||
ignorePrev = true
|
||
}
|
||
if wrapped {
|
||
// if a field has wrapped, we need to get the next row for the
|
||
// previous field, and if that one wraps we need to keep going
|
||
// backward.
|
||
for j := i - 1; j >= 0; j-- {
|
||
nextRow, rowID, value, wrapped, err := gbi.rowIters[j].Next()
|
||
if err != nil {
|
||
return nil, err
|
||
} else if nextRow == nil {
|
||
gbi.done = true
|
||
return gbi, nil
|
||
}
|
||
gbi.rows[j].row = nextRow
|
||
gbi.rows[j].id = rowID
|
||
gbi.rows[j].value = value
|
||
if !wrapped {
|
||
break
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// Apply filter to first level, if available.
|
||
if gbi.filter != nil && len(gbi.rows) > 0 {
|
||
gbi.rows[0].row = gbi.rows[0].row.Intersect(gbi.filter)
|
||
}
|
||
|
||
for i := 1; i < len(gbi.rows)-1; i++ {
|
||
gbi.rows[i].row = gbi.rows[i].row.Intersect(gbi.rows[i-1].row)
|
||
}
|
||
|
||
return gbi, nil
|
||
}
|
||
|
||
// nextAtIdx is a recursive helper method for getting the next row for the field
|
||
// at index i, and then updating the rows in the "higher" fields if it wraps.
|
||
func (gbi *groupByIterator) nextAtIdx(ctx context.Context, i int) (err error) {
|
||
// loop until we find a non-empty row. This is an optimization - the loop and if/break can be removed.
|
||
for {
|
||
if err = ctx.Err(); err != nil {
|
||
return err
|
||
}
|
||
nr, rowID, value, wrapped, err := gbi.rowIters[i].Next()
|
||
if err != nil {
|
||
return err
|
||
} else if nr == nil {
|
||
gbi.done = true
|
||
return nil
|
||
}
|
||
if wrapped && i != 0 {
|
||
err = gbi.nextAtIdx(ctx, i-1)
|
||
if gbi.done || err != nil {
|
||
return err
|
||
}
|
||
}
|
||
if i == 0 && gbi.filter != nil {
|
||
gbi.rows[i].row = nr.Intersect(gbi.filter)
|
||
} else if i == 0 || i == len(gbi.rows)-1 {
|
||
gbi.rows[i].row = nr
|
||
} else {
|
||
gbi.rows[i].row = nr.Intersect(gbi.rows[i-1].row)
|
||
}
|
||
gbi.rows[i].id = rowID
|
||
gbi.rows[i].value = value
|
||
|
||
if !gbi.rows[i].row.IsEmpty() {
|
||
break
|
||
}
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// Next returns a GroupCount representing the next group by record. When there
|
||
// are no more records it will return an empty GroupCount and done==true.
|
||
func (gbi *groupByIterator) Next(ctx context.Context) (ret GroupCount, done bool, err error) {
|
||
// loop until we find a result with count > 0
|
||
for {
|
||
if err := ctx.Err(); err != nil {
|
||
return ret, false, err
|
||
}
|
||
if gbi.done {
|
||
return ret, true, nil
|
||
}
|
||
if gbi.aggregate == nil {
|
||
if len(gbi.rows) == 1 {
|
||
ret.Count = gbi.rows[len(gbi.rows)-1].row.Count()
|
||
} else {
|
||
ret.Count = gbi.rows[len(gbi.rows)-1].row.intersectionCount(gbi.rows[len(gbi.rows)-2].row)
|
||
}
|
||
} else {
|
||
gr := gbi.rows[len(gbi.rows)-1]
|
||
filter := gr.row
|
||
if len(gbi.rows) != 1 {
|
||
filter = filter.Intersect(gbi.rows[len(gbi.rows)-2].row)
|
||
}
|
||
|
||
switch gbi.aggregate.Name {
|
||
case "Sum":
|
||
result, err := gbi.executor.executeSumCountShard(ctx, gbi.qcx, gbi.index, gbi.aggregate, filter, gbi.shard)
|
||
if err != nil {
|
||
return ret, false, err
|
||
}
|
||
ret.Count = uint64(result.Count)
|
||
ret.Sum = result.Val
|
||
}
|
||
}
|
||
if ret.Count == 0 {
|
||
err := gbi.nextAtIdx(ctx, len(gbi.rows)-1)
|
||
if err != nil {
|
||
return ret, false, err
|
||
}
|
||
continue
|
||
}
|
||
break
|
||
}
|
||
|
||
ret.Group = make([]FieldRow, len(gbi.rows))
|
||
copy(ret.Group, gbi.fields)
|
||
for i, r := range gbi.rows {
|
||
|
||
ret.Group[i].RowID = r.id
|
||
ret.Group[i].Value = r.value
|
||
}
|
||
|
||
// set up for next call
|
||
err = gbi.nextAtIdx(ctx, len(gbi.rows)-1)
|
||
|
||
return ret, false, err
|
||
}
|
||
|
||
// getCondIntSlice looks at the field, the cond op type (which is
|
||
// expected to be one of the BETWEEN ops types), and the values in the
|
||
// conditional and returns a slice of int64 which is scaled for
|
||
// decimal fields and has the values modulated such that the BETWEEN
|
||
// op can be treated as being of the form a<=x<=b.
|
||
func getCondIntSlice(f *Field, cond *pql.Condition) ([]int64, error) {
|
||
val, ok := cond.Value.([]interface{})
|
||
if !ok {
|
||
return nil, errors.Errorf("expected conditional to have []interface{} Value, but got %v of %[1]T", cond.Value)
|
||
}
|
||
|
||
ret := make([]int64, len(val))
|
||
for i, v := range val {
|
||
s, err := getScaledInt(f, v)
|
||
if err != nil {
|
||
return nil, errors.Wrap(err, "getting scaled integer")
|
||
}
|
||
ret[i] = s
|
||
}
|
||
|
||
// In the case where one (or both) of the predicates is on the
|
||
// opposite edge, return early to avoid the increment/decrement
|
||
// logic below and prevent an overflow.
|
||
if ret[0] == math.MaxInt64 || ret[1] == math.MinInt64 {
|
||
return ret, nil
|
||
}
|
||
|
||
switch cond.Op {
|
||
case pql.BTWN_LT_LTE: // a < x <= b
|
||
ret[0]++
|
||
case pql.BTWN_LTE_LT: // a <= x < b
|
||
ret[1]--
|
||
case pql.BTWN_LT_LT: // a < x < b
|
||
ret[0]++
|
||
ret[1]--
|
||
}
|
||
|
||
return ret, nil
|
||
}
|
||
|
||
// getScaledInt gets the scaled integer value for v based on
|
||
// the field type. In the `decimalToInt64()` function, the
|
||
// returned int64 value will be adjusted to correspond to the
|
||
// range of the field. This is only necessary for pql.Decimal
|
||
// values. For example, if v is less than f.Options.Min, int64 will
|
||
// return int64(f.Options.Min)-1, or math.MinInt64 if f.Options.Min
|
||
// is already equal to math.MinInt64.
|
||
func getScaledInt(f *Field, v interface{}) (int64, error) {
|
||
var value int64
|
||
|
||
opt := f.Options()
|
||
if opt.Type == FieldTypeDecimal {
|
||
switch tv := v.(type) {
|
||
case uint64:
|
||
if tv > math.MaxInt64 {
|
||
return 0, errors.Errorf("uint64 value out of range for pql.Decimal: %d", tv)
|
||
}
|
||
dec := pql.NewDecimal(int64(tv), 0)
|
||
value = decimalToInt64(dec, opt)
|
||
case int64:
|
||
dec := pql.NewDecimal(tv, 0)
|
||
value = decimalToInt64(dec, opt)
|
||
case pql.Decimal:
|
||
value = decimalToInt64(tv, opt)
|
||
case float64:
|
||
value = int64(tv * math.Pow10(int(opt.Scale)))
|
||
default:
|
||
return 0, errors.Errorf("unexpected decimal value type %T, val %v", tv, tv)
|
||
}
|
||
} else {
|
||
switch tv := v.(type) {
|
||
case int64:
|
||
value = tv
|
||
case uint64:
|
||
value = int64(tv)
|
||
default:
|
||
return 0, errors.Errorf("unexpected value type %T, val %v", tv, tv)
|
||
}
|
||
}
|
||
return value, nil
|
||
}
|
||
|
||
func decimalToInt64(dec pql.Decimal, opt FieldOptions) int64 {
|
||
scale := opt.Scale
|
||
if dec.GreaterThanOrEqualTo(opt.Min) && dec.LessThanOrEqualTo(opt.Max) {
|
||
return dec.ToInt64(scale)
|
||
} else if dec.LessThan(opt.Min) {
|
||
value := opt.Min.ToInt64(scale)
|
||
if value != math.MinInt64 {
|
||
value--
|
||
}
|
||
return value
|
||
} else if dec.GreaterThan(opt.Max) {
|
||
value := opt.Max.ToInt64(scale)
|
||
if value != math.MaxInt64 {
|
||
value++
|
||
}
|
||
return value
|
||
}
|
||
|
||
return 0
|
||
}
|