277 lines
6.6 KiB
Go
277 lines
6.6 KiB
Go
package execute
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/influxdata/platform/query"
|
|
"github.com/influxdata/platform/query/interpreter"
|
|
"github.com/influxdata/platform/query/semantic"
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
type aggregateTransformation struct {
|
|
d Dataset
|
|
cache TableBuilderCache
|
|
agg Aggregate
|
|
|
|
config AggregateConfig
|
|
}
|
|
|
|
type AggregateConfig struct {
|
|
Columns []string `json:"columns"`
|
|
TimeSrc string `json:"timeSrc"`
|
|
TimeDst string `json:"timeDst"`
|
|
}
|
|
|
|
var DefaultAggregateConfig = AggregateConfig{
|
|
Columns: []string{DefaultValueColLabel},
|
|
TimeSrc: DefaultStopColLabel,
|
|
TimeDst: DefaultTimeColLabel,
|
|
}
|
|
|
|
func (c AggregateConfig) Copy() AggregateConfig {
|
|
nc := c
|
|
if c.Columns != nil {
|
|
nc.Columns = make([]string, len(c.Columns))
|
|
copy(nc.Columns, c.Columns)
|
|
}
|
|
return nc
|
|
}
|
|
|
|
func (c *AggregateConfig) ReadArgs(args query.Arguments) error {
|
|
if label, ok, err := args.GetString("timeDst"); err != nil {
|
|
return err
|
|
} else if ok {
|
|
c.TimeDst = label
|
|
} else {
|
|
c.TimeDst = DefaultAggregateConfig.TimeDst
|
|
}
|
|
|
|
if timeValue, ok, err := args.GetString("timeSrc"); err != nil {
|
|
return err
|
|
} else if ok {
|
|
c.TimeSrc = timeValue
|
|
} else {
|
|
c.TimeSrc = DefaultAggregateConfig.TimeSrc
|
|
}
|
|
|
|
if cols, ok, err := args.GetArray("columns", semantic.String); err != nil {
|
|
return err
|
|
} else if ok {
|
|
columns, err := interpreter.ToStringArray(cols)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
c.Columns = columns
|
|
} else {
|
|
c.Columns = DefaultAggregateConfig.Columns
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func NewAggregateTransformation(d Dataset, c TableBuilderCache, agg Aggregate, config AggregateConfig) *aggregateTransformation {
|
|
return &aggregateTransformation{
|
|
d: d,
|
|
cache: c,
|
|
agg: agg,
|
|
config: config,
|
|
}
|
|
}
|
|
|
|
func NewAggregateTransformationAndDataset(id DatasetID, mode AccumulationMode, agg Aggregate, config AggregateConfig, a *Allocator) (*aggregateTransformation, Dataset) {
|
|
cache := NewTableBuilderCache(a)
|
|
d := NewDataset(id, mode, cache)
|
|
return NewAggregateTransformation(d, cache, agg, config), d
|
|
}
|
|
|
|
func (t *aggregateTransformation) RetractTable(id DatasetID, key query.GroupKey) error {
|
|
//TODO(nathanielc): Store intermediate state for retractions
|
|
return t.d.RetractTable(key)
|
|
}
|
|
|
|
func (t *aggregateTransformation) Process(id DatasetID, tbl query.Table) error {
|
|
builder, new := t.cache.TableBuilder(tbl.Key())
|
|
if !new {
|
|
return fmt.Errorf("aggregate found duplicate table with key: %v", tbl.Key())
|
|
}
|
|
|
|
AddTableKeyCols(tbl.Key(), builder)
|
|
builder.AddCol(query.ColMeta{
|
|
Label: t.config.TimeDst,
|
|
Type: query.TTime,
|
|
})
|
|
|
|
builderColMap := make([]int, len(t.config.Columns))
|
|
tableColMap := make([]int, len(t.config.Columns))
|
|
aggregates := make([]ValueFunc, len(t.config.Columns))
|
|
|
|
cols := tbl.Cols()
|
|
for j, label := range t.config.Columns {
|
|
idx := -1
|
|
for bj, bc := range cols {
|
|
if bc.Label == label {
|
|
idx = bj
|
|
break
|
|
}
|
|
}
|
|
if idx < 0 {
|
|
return fmt.Errorf("column %q does not exist", label)
|
|
}
|
|
c := cols[idx]
|
|
if tbl.Key().HasCol(c.Label) {
|
|
return errors.New("cannot aggregate columns that are part of the group key")
|
|
}
|
|
var vf ValueFunc
|
|
switch c.Type {
|
|
case query.TBool:
|
|
vf = t.agg.NewBoolAgg()
|
|
case query.TInt:
|
|
vf = t.agg.NewIntAgg()
|
|
case query.TUInt:
|
|
vf = t.agg.NewUIntAgg()
|
|
case query.TFloat:
|
|
vf = t.agg.NewFloatAgg()
|
|
case query.TString:
|
|
vf = t.agg.NewStringAgg()
|
|
}
|
|
if vf == nil {
|
|
return fmt.Errorf("unsupported aggregate column type %v", c.Type)
|
|
}
|
|
aggregates[j] = vf
|
|
builderColMap[j] = builder.AddCol(query.ColMeta{
|
|
Label: c.Label,
|
|
Type: vf.Type(),
|
|
})
|
|
tableColMap[j] = idx
|
|
}
|
|
if err := AppendAggregateTime(t.config.TimeSrc, t.config.TimeDst, tbl.Key(), builder); err != nil {
|
|
return err
|
|
}
|
|
|
|
tbl.Do(func(cr query.ColReader) error {
|
|
for j := range t.config.Columns {
|
|
vf := aggregates[j]
|
|
|
|
tj := tableColMap[j]
|
|
c := tbl.Cols()[tj]
|
|
|
|
switch c.Type {
|
|
case query.TBool:
|
|
vf.(DoBoolAgg).DoBool(cr.Bools(tj))
|
|
case query.TInt:
|
|
vf.(DoIntAgg).DoInt(cr.Ints(tj))
|
|
case query.TUInt:
|
|
vf.(DoUIntAgg).DoUInt(cr.UInts(tj))
|
|
case query.TFloat:
|
|
vf.(DoFloatAgg).DoFloat(cr.Floats(tj))
|
|
case query.TString:
|
|
vf.(DoStringAgg).DoString(cr.Strings(tj))
|
|
default:
|
|
return fmt.Errorf("unsupport aggregate type %v", c.Type)
|
|
}
|
|
}
|
|
return nil
|
|
})
|
|
for j, vf := range aggregates {
|
|
bj := builderColMap[j]
|
|
// Append aggregated value
|
|
switch vf.Type() {
|
|
case query.TBool:
|
|
builder.AppendBool(bj, vf.(BoolValueFunc).ValueBool())
|
|
case query.TInt:
|
|
builder.AppendInt(bj, vf.(IntValueFunc).ValueInt())
|
|
case query.TUInt:
|
|
builder.AppendUInt(bj, vf.(UIntValueFunc).ValueUInt())
|
|
case query.TFloat:
|
|
builder.AppendFloat(bj, vf.(FloatValueFunc).ValueFloat())
|
|
case query.TString:
|
|
builder.AppendString(bj, vf.(StringValueFunc).ValueString())
|
|
}
|
|
}
|
|
|
|
AppendKeyValues(tbl.Key(), builder)
|
|
|
|
return nil
|
|
}
|
|
|
|
func (t *aggregateTransformation) UpdateWatermark(id DatasetID, mark Time) error {
|
|
return t.d.UpdateWatermark(mark)
|
|
}
|
|
func (t *aggregateTransformation) UpdateProcessingTime(id DatasetID, pt Time) error {
|
|
return t.d.UpdateProcessingTime(pt)
|
|
}
|
|
func (t *aggregateTransformation) Finish(id DatasetID, err error) {
|
|
t.d.Finish(err)
|
|
}
|
|
|
|
func AppendAggregateTime(srcTime, dstTime string, key query.GroupKey, builder TableBuilder) error {
|
|
srcTimeIdx := ColIdx(srcTime, key.Cols())
|
|
if srcTimeIdx < 0 {
|
|
return fmt.Errorf("timeSrc column %q does not exist", srcTime)
|
|
}
|
|
srcTimeCol := key.Cols()[srcTimeIdx]
|
|
if srcTimeCol.Type != query.TTime {
|
|
return fmt.Errorf("timeSrc column %q does not have type time", srcTime)
|
|
}
|
|
|
|
dstTimeIdx := ColIdx(dstTime, builder.Cols())
|
|
if dstTimeIdx < 0 {
|
|
return fmt.Errorf("timeDst column %q does not exist", dstTime)
|
|
}
|
|
dstTimeCol := builder.Cols()[dstTimeIdx]
|
|
if dstTimeCol.Type != query.TTime {
|
|
return fmt.Errorf("timeDst column %q does not have type time", dstTime)
|
|
}
|
|
|
|
builder.AppendTime(dstTimeIdx, key.ValueTime(srcTimeIdx))
|
|
return nil
|
|
}
|
|
|
|
type Aggregate interface {
|
|
NewBoolAgg() DoBoolAgg
|
|
NewIntAgg() DoIntAgg
|
|
NewUIntAgg() DoUIntAgg
|
|
NewFloatAgg() DoFloatAgg
|
|
NewStringAgg() DoStringAgg
|
|
}
|
|
|
|
type ValueFunc interface {
|
|
Type() query.DataType
|
|
}
|
|
type DoBoolAgg interface {
|
|
ValueFunc
|
|
DoBool([]bool)
|
|
}
|
|
type DoFloatAgg interface {
|
|
ValueFunc
|
|
DoFloat([]float64)
|
|
}
|
|
type DoIntAgg interface {
|
|
ValueFunc
|
|
DoInt([]int64)
|
|
}
|
|
type DoUIntAgg interface {
|
|
ValueFunc
|
|
DoUInt([]uint64)
|
|
}
|
|
type DoStringAgg interface {
|
|
ValueFunc
|
|
DoString([]string)
|
|
}
|
|
|
|
type BoolValueFunc interface {
|
|
ValueBool() bool
|
|
}
|
|
type FloatValueFunc interface {
|
|
ValueFloat() float64
|
|
}
|
|
type IntValueFunc interface {
|
|
ValueInt() int64
|
|
}
|
|
type UIntValueFunc interface {
|
|
ValueUInt() uint64
|
|
}
|
|
type StringValueFunc interface {
|
|
ValueString() string
|
|
}
|