influxdb/query/functions/range.go

176 lines
4.6 KiB
Go

package functions
import (
"fmt"
"github.com/influxdata/platform/query"
"github.com/influxdata/platform/query/execute"
"github.com/influxdata/platform/query/plan"
"github.com/influxdata/platform/query/semantic"
)
const RangeKind = "range"
type RangeOpSpec struct {
Start query.Time `json:"start"`
Stop query.Time `json:"stop"`
}
var rangeSignature = query.DefaultFunctionSignature()
func init() {
rangeSignature.Params["start"] = semantic.Time
rangeSignature.Params["stop"] = semantic.Time
query.RegisterFunction(RangeKind, createRangeOpSpec, rangeSignature)
query.RegisterOpSpec(RangeKind, newRangeOp)
plan.RegisterProcedureSpec(RangeKind, newRangeProcedure, RangeKind)
// TODO register a range transformation. Currently range is only supported if it is pushed down into a select procedure.
execute.RegisterTransformation(RangeKind, createRangeTransformation)
}
func createRangeOpSpec(args query.Arguments, a *query.Administration) (query.OperationSpec, error) {
if err := a.AddParentFromArgs(args); err != nil {
return nil, err
}
start, err := args.GetRequiredTime("start")
if err != nil {
return nil, err
}
spec := &RangeOpSpec{
Start: start,
}
if stop, ok, err := args.GetTime("stop"); err != nil {
return nil, err
} else if ok {
spec.Stop = stop
} else {
// Make stop time implicit "now"
spec.Stop.IsRelative = true
}
return spec, nil
}
func newRangeOp() query.OperationSpec {
return new(RangeOpSpec)
}
func (s *RangeOpSpec) Kind() query.OperationKind {
return RangeKind
}
type RangeProcedureSpec struct {
Bounds plan.BoundsSpec
}
func newRangeProcedure(qs query.OperationSpec, pa plan.Administration) (plan.ProcedureSpec, error) {
spec, ok := qs.(*RangeOpSpec)
if !ok {
return nil, fmt.Errorf("invalid spec type %T", qs)
}
return &RangeProcedureSpec{
Bounds: plan.BoundsSpec{
Start: spec.Start,
Stop: spec.Stop,
},
}, nil
}
func (s *RangeProcedureSpec) Kind() plan.ProcedureKind {
return RangeKind
}
func (s *RangeProcedureSpec) Copy() plan.ProcedureSpec {
ns := new(RangeProcedureSpec)
ns.Bounds = s.Bounds
return ns
}
func (s *RangeProcedureSpec) PushDownRules() []plan.PushDownRule {
return []plan.PushDownRule{{
Root: FromKind,
Through: []plan.ProcedureKind{GroupKind, LimitKind, FilterKind},
}}
}
func (s *RangeProcedureSpec) PushDown(root *plan.Procedure, dup func() *plan.Procedure) {
selectSpec := root.Spec.(*FromProcedureSpec)
if selectSpec.BoundsSet {
// Example case where this matters
// var data = select(database: "mydb")
// var past = data.range(start:-2d,stop:-1d)
// var current = data.range(start:-1d,stop:now)
root = dup()
selectSpec = root.Spec.(*FromProcedureSpec)
selectSpec.BoundsSet = false
selectSpec.Bounds = plan.BoundsSpec{}
return
}
selectSpec.BoundsSet = true
selectSpec.Bounds = s.Bounds
}
func (s *RangeProcedureSpec) TimeBounds() plan.BoundsSpec {
return s.Bounds
}
func createRangeTransformation(id execute.DatasetID, mode execute.AccumulationMode, spec plan.ProcedureSpec, a execute.Administration) (execute.Transformation, execute.Dataset, error) {
s, ok := spec.(*RangeProcedureSpec)
if !ok {
return nil, nil, fmt.Errorf("invalid spec type %T", spec)
}
cache := execute.NewBlockBuilderCache(a.Allocator())
d := execute.NewDataset(id, mode, cache)
t, err := NewRangeTransformation(d, cache, s)
if err != nil {
return nil, nil, err
}
return t, d, nil
}
type rangeTransformation struct {
d execute.Dataset
cache execute.BlockBuilderCache
Start query.Time
Stop query.Time
}
func NewRangeTransformation(d execute.Dataset, cache execute.BlockBuilderCache, spec *RangeProcedureSpec) (*rangeTransformation, error) {
return &rangeTransformation{
d: d,
cache: cache,
Start: spec.Bounds.Start,
Stop: spec.Bounds.Stop,
}, nil
}
func (t *rangeTransformation) RetractBlock(id execute.DatasetID, key query.PartitionKey) error {
return t.d.RetractBlock(key)
}
func (t *rangeTransformation) Process(id execute.DatasetID, b query.Block) error {
builder, created := t.cache.BlockBuilder(b.Key())
if !created {
return fmt.Errorf("range found duplicate block with key: %v", b.Key())
}
execute.AddBlockCols(b, builder)
cols := make([]int, len(b.Cols()))
for i := range cols {
cols[i] = i
}
execute.AppendBlock(b, builder, cols)
return nil
}
func (t *rangeTransformation) UpdateWatermark(id execute.DatasetID, mark execute.Time) error {
return t.d.UpdateWatermark(mark)
}
func (t *rangeTransformation) UpdateProcessingTime(id execute.DatasetID, pt execute.Time) error {
return t.d.UpdateProcessingTime(pt)
}
func (t *rangeTransformation) Finish(id execute.DatasetID, err error) {
t.d.Finish(err)
}