489 lines
15 KiB
Go
489 lines
15 KiB
Go
package functions_test
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/influxdata/platform/query/semantic"
|
|
"github.com/influxdata/platform/query/values"
|
|
"github.com/pkg/errors"
|
|
|
|
"github.com/influxdata/platform/query"
|
|
"github.com/influxdata/platform/query/execute"
|
|
"github.com/influxdata/platform/query/execute/executetest"
|
|
"github.com/influxdata/platform/query/functions"
|
|
"github.com/influxdata/platform/query/plan"
|
|
"github.com/influxdata/platform/query/plan/plantest"
|
|
"github.com/influxdata/platform/query/querytest"
|
|
)
|
|
|
|
func TestRange_NewQuery(t *testing.T) {
|
|
tests := []querytest.NewQueryTestCase{
|
|
{
|
|
Name: "from with database with range",
|
|
Raw: `from(db:"mydb") |> range(start:-4h, stop:-2h) |> sum()`,
|
|
Want: &query.Spec{
|
|
Operations: []*query.Operation{
|
|
{
|
|
ID: "from0",
|
|
Spec: &functions.FromOpSpec{
|
|
Database: "mydb",
|
|
},
|
|
},
|
|
{
|
|
ID: "range1",
|
|
Spec: &functions.RangeOpSpec{
|
|
Start: query.Time{
|
|
Relative: -4 * time.Hour,
|
|
IsRelative: true,
|
|
},
|
|
Stop: query.Time{
|
|
Relative: -2 * time.Hour,
|
|
IsRelative: true,
|
|
},
|
|
TimeCol: "_time",
|
|
StartCol: "_start",
|
|
StopCol: "_stop",
|
|
},
|
|
},
|
|
{
|
|
ID: "sum2",
|
|
Spec: &functions.SumOpSpec{
|
|
AggregateConfig: execute.DefaultAggregateConfig,
|
|
},
|
|
},
|
|
},
|
|
Edges: []query.Edge{
|
|
{Parent: "from0", Child: "range1"},
|
|
{Parent: "range1", Child: "sum2"},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Name: "from csv with range",
|
|
Raw: `fromCSV(csv: "1,2") |> range(start:-4h, stop:-2h, timeCol: "_start") |> sum()`,
|
|
Want: &query.Spec{
|
|
Operations: []*query.Operation{
|
|
{
|
|
ID: "fromCSV0",
|
|
Spec: &functions.FromCSVOpSpec{
|
|
CSV: "1,2",
|
|
},
|
|
},
|
|
{
|
|
ID: "range1",
|
|
Spec: &functions.RangeOpSpec{
|
|
Start: query.Time{
|
|
Relative: -4 * time.Hour,
|
|
IsRelative: true,
|
|
},
|
|
Stop: query.Time{
|
|
Relative: -2 * time.Hour,
|
|
IsRelative: true,
|
|
},
|
|
TimeCol: "_start",
|
|
StartCol: "_start",
|
|
StopCol: "_stop",
|
|
},
|
|
},
|
|
{
|
|
ID: "sum2",
|
|
Spec: &functions.SumOpSpec{
|
|
AggregateConfig: execute.DefaultAggregateConfig,
|
|
},
|
|
},
|
|
},
|
|
Edges: []query.Edge{
|
|
{Parent: "fromCSV0", Child: "range1"},
|
|
{Parent: "range1", Child: "sum2"},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
for _, tc := range tests {
|
|
tc := tc
|
|
t.Run(tc.Name, func(t *testing.T) {
|
|
t.Parallel()
|
|
querytest.NewQueryTestHelper(t, tc)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRangeOperation_Marshaling(t *testing.T) {
|
|
data := []byte(`{"id":"range","kind":"range","spec":{"start":"-1h","stop":"2017-10-10T00:00:00Z"}}`)
|
|
op := &query.Operation{
|
|
ID: "range",
|
|
Spec: &functions.RangeOpSpec{
|
|
Start: query.Time{
|
|
Relative: -1 * time.Hour,
|
|
IsRelative: true,
|
|
},
|
|
Stop: query.Time{
|
|
Absolute: time.Date(2017, 10, 10, 0, 0, 0, 0, time.UTC),
|
|
},
|
|
},
|
|
}
|
|
|
|
querytest.OperationMarshalingTestHelper(t, data, op)
|
|
}
|
|
|
|
func TestRange_PushDown(t *testing.T) {
|
|
spec := &functions.RangeProcedureSpec{
|
|
Bounds: plan.BoundsSpec{
|
|
Stop: query.Now,
|
|
},
|
|
}
|
|
root := &plan.Procedure{
|
|
Spec: new(functions.FromProcedureSpec),
|
|
}
|
|
want := &plan.Procedure{
|
|
Spec: &functions.FromProcedureSpec{
|
|
BoundsSet: true,
|
|
Bounds: plan.BoundsSpec{
|
|
Stop: query.Now,
|
|
},
|
|
},
|
|
}
|
|
|
|
plantest.PhysicalPlan_PushDown_TestHelper(t, spec, root, false, want)
|
|
}
|
|
|
|
func TestRange_Process(t *testing.T) {
|
|
testCases := []struct {
|
|
name string
|
|
spec *functions.RangeProcedureSpec
|
|
data []query.Table
|
|
want []*executetest.Table
|
|
groupKey func() query.GroupKey
|
|
now values.Time
|
|
wantErr error
|
|
}{
|
|
{
|
|
name: "from csv",
|
|
spec: &functions.RangeProcedureSpec{
|
|
Bounds: plan.BoundsSpec{
|
|
Start: query.Time{
|
|
IsRelative: true,
|
|
Relative: -5 * time.Minute,
|
|
},
|
|
Stop: query.Time{
|
|
IsRelative: true,
|
|
Relative: -2 * time.Minute,
|
|
},
|
|
},
|
|
TimeCol: "_time",
|
|
StartCol: "_start",
|
|
StopCol: "_stop",
|
|
},
|
|
data: []query.Table{&executetest.Table{
|
|
ColMeta: []query.ColMeta{
|
|
{Label: "_time", Type: query.TTime},
|
|
{Label: "_value", Type: query.TFloat},
|
|
},
|
|
Data: [][]interface{}{
|
|
{execute.Time(time.Minute.Nanoseconds()), 10.0},
|
|
{execute.Time(2 * time.Minute.Nanoseconds()), 5.0},
|
|
{execute.Time(3 * time.Minute.Nanoseconds()), 9.0},
|
|
{execute.Time(4 * time.Minute.Nanoseconds()), 4.0},
|
|
{execute.Time(5 * time.Minute.Nanoseconds()), 6.0},
|
|
{execute.Time(6 * time.Minute.Nanoseconds()), 8.0},
|
|
{execute.Time(7 * time.Minute.Nanoseconds()), 1.0},
|
|
},
|
|
}},
|
|
want: []*executetest.Table{{
|
|
ColMeta: []query.ColMeta{
|
|
{Label: "_time", Type: query.TTime},
|
|
{Label: "_value", Type: query.TFloat},
|
|
{Label: "_start", Type: query.TTime},
|
|
{Label: "_stop", Type: query.TTime},
|
|
},
|
|
Data: [][]interface{}{
|
|
{execute.Time(2 * time.Minute.Nanoseconds()), 5.0, execute.Time(2 * time.Minute.Nanoseconds()), execute.Time(5 * time.Minute.Nanoseconds())},
|
|
{execute.Time(3 * time.Minute.Nanoseconds()), 9.0, execute.Time(2 * time.Minute.Nanoseconds()), execute.Time(5 * time.Minute.Nanoseconds())},
|
|
{execute.Time(4 * time.Minute.Nanoseconds()), 4.0, execute.Time(2 * time.Minute.Nanoseconds()), execute.Time(5 * time.Minute.Nanoseconds())},
|
|
},
|
|
}},
|
|
now: values.Time(7 * time.Minute.Nanoseconds()),
|
|
},
|
|
{
|
|
name: "invalid column",
|
|
spec: &functions.RangeProcedureSpec{
|
|
Bounds: plan.BoundsSpec{
|
|
Start: query.Time{
|
|
IsRelative: true,
|
|
Relative: -5 * time.Minute,
|
|
},
|
|
Stop: query.Time{
|
|
IsRelative: true,
|
|
Relative: -2 * time.Minute,
|
|
},
|
|
},
|
|
TimeCol: "_value",
|
|
StartCol: "_start",
|
|
StopCol: "_stop",
|
|
},
|
|
data: []query.Table{&executetest.Table{
|
|
ColMeta: []query.ColMeta{
|
|
{Label: "_time", Type: query.TTime},
|
|
{Label: "_value", Type: query.TFloat},
|
|
},
|
|
Data: [][]interface{}{
|
|
{execute.Time(time.Minute.Nanoseconds()), 10.0},
|
|
{execute.Time(3 * time.Minute.Nanoseconds()), 9.0},
|
|
{execute.Time(7 * time.Minute.Nanoseconds()), 1.0},
|
|
{execute.Time(2 * time.Minute.Nanoseconds()), 5.0},
|
|
{execute.Time(4 * time.Minute.Nanoseconds()), 4.0},
|
|
{execute.Time(6 * time.Minute.Nanoseconds()), 8.0},
|
|
{execute.Time(5 * time.Minute.Nanoseconds()), 6.0},
|
|
},
|
|
}},
|
|
want: []*executetest.Table{{
|
|
ColMeta: []query.ColMeta{
|
|
{Label: "_time", Type: query.TTime},
|
|
{Label: "_value", Type: query.TFloat},
|
|
},
|
|
Data: [][]interface{}(nil),
|
|
}},
|
|
wantErr: errors.New("range error: provided column _value is not of type time"),
|
|
now: values.Time(7 * time.Minute.Nanoseconds()),
|
|
},
|
|
{
|
|
name: "specified column",
|
|
spec: &functions.RangeProcedureSpec{
|
|
Bounds: plan.BoundsSpec{
|
|
Start: query.Time{
|
|
IsRelative: true,
|
|
Relative: -2 * time.Minute,
|
|
},
|
|
Stop: query.Time{
|
|
IsRelative: true,
|
|
},
|
|
},
|
|
TimeCol: "_start",
|
|
StartCol: "_start",
|
|
StopCol: "_stop",
|
|
},
|
|
data: []query.Table{&executetest.Table{
|
|
ColMeta: []query.ColMeta{
|
|
{Label: "_start", Type: query.TTime},
|
|
{Label: "_time", Type: query.TTime},
|
|
{Label: "_value", Type: query.TFloat},
|
|
},
|
|
Data: [][]interface{}{
|
|
{execute.Time(0), execute.Time(time.Minute.Nanoseconds()), 10.0},
|
|
{execute.Time(time.Minute.Nanoseconds()), execute.Time(3 * time.Minute.Nanoseconds()), 9.0},
|
|
{execute.Time(2 * time.Minute.Nanoseconds()), execute.Time(7 * time.Minute.Nanoseconds()), 1.0},
|
|
{execute.Time(3 * time.Minute.Nanoseconds()), execute.Time(4 * time.Minute.Nanoseconds()), 4.0},
|
|
},
|
|
}},
|
|
want: []*executetest.Table{{
|
|
ColMeta: []query.ColMeta{
|
|
{Label: "_start", Type: query.TTime},
|
|
{Label: "_time", Type: query.TTime},
|
|
{Label: "_value", Type: query.TFloat},
|
|
{Label: "_stop", Type: query.TTime},
|
|
},
|
|
Data: [][]interface{}{
|
|
{execute.Time(time.Minute.Nanoseconds()), execute.Time(3 * time.Minute.Nanoseconds()), 9.0, execute.Time(3 * time.Minute.Nanoseconds())},
|
|
{execute.Time(2 * time.Minute.Nanoseconds()), execute.Time(7 * time.Minute.Nanoseconds()), 1.0, execute.Time(3 * time.Minute.Nanoseconds())},
|
|
},
|
|
}},
|
|
now: values.Time(3 * time.Minute.Nanoseconds()),
|
|
},
|
|
{
|
|
name: "group key no overlap",
|
|
spec: &functions.RangeProcedureSpec{
|
|
Bounds: plan.BoundsSpec{
|
|
Start: query.Time{
|
|
IsRelative: true,
|
|
Relative: -2 * time.Minute,
|
|
},
|
|
Stop: query.Time{
|
|
IsRelative: true,
|
|
},
|
|
},
|
|
TimeCol: "_start",
|
|
StartCol: "_start",
|
|
StopCol: "_stop",
|
|
},
|
|
data: []query.Table{&executetest.Table{
|
|
ColMeta: []query.ColMeta{
|
|
{Label: "_start", Type: query.TTime},
|
|
{Label: "_stop", Type: query.TTime},
|
|
{Label: "_time", Type: query.TTime},
|
|
{Label: "_value", Type: query.TFloat},
|
|
},
|
|
KeyCols: []string{"_start", "_stop"},
|
|
KeyValues: []interface{}{execute.Time(10 * time.Minute.Nanoseconds()), execute.Time(20 * time.Minute.Nanoseconds())},
|
|
Data: [][]interface{}{
|
|
{execute.Time(10 * time.Minute.Nanoseconds()), execute.Time(20 * time.Minute.Nanoseconds()), execute.Time(11 * time.Minute.Nanoseconds()), 10.0},
|
|
{execute.Time(10 * time.Minute.Nanoseconds()), execute.Time(20 * time.Minute.Nanoseconds()), execute.Time(12 * time.Minute.Nanoseconds()), 9.0},
|
|
{execute.Time(10 * time.Minute.Nanoseconds()), execute.Time(20 * time.Minute.Nanoseconds()), execute.Time(13 * time.Minute.Nanoseconds()), 1.0},
|
|
{execute.Time(10 * time.Minute.Nanoseconds()), execute.Time(20 * time.Minute.Nanoseconds()), execute.Time(14 * time.Minute.Nanoseconds()), 4.0},
|
|
},
|
|
}},
|
|
want: []*executetest.Table{{
|
|
ColMeta: []query.ColMeta{
|
|
{Label: "_start", Type: query.TTime},
|
|
{Label: "_stop", Type: query.TTime},
|
|
{Label: "_time", Type: query.TTime},
|
|
{Label: "_value", Type: query.TFloat},
|
|
},
|
|
KeyCols: []string{"_start", "_stop"},
|
|
KeyValues: []interface{}{execute.Time(10 * time.Minute.Nanoseconds()), execute.Time(20 * time.Minute.Nanoseconds())},
|
|
Data: [][]interface{}(nil),
|
|
}},
|
|
now: values.Time(3 * time.Minute.Nanoseconds()),
|
|
},
|
|
{
|
|
name: "group key overlap",
|
|
spec: &functions.RangeProcedureSpec{
|
|
Bounds: plan.BoundsSpec{
|
|
Start: query.Time{
|
|
Absolute: time.Unix(12*time.Minute.Nanoseconds(), 0),
|
|
},
|
|
Stop: query.Time{
|
|
Absolute: time.Unix(14*time.Minute.Nanoseconds(), 0),
|
|
},
|
|
},
|
|
TimeCol: "_time",
|
|
StartCol: "_start",
|
|
StopCol: "_stop",
|
|
},
|
|
data: []query.Table{&executetest.Table{
|
|
ColMeta: []query.ColMeta{
|
|
{Label: "_start", Type: query.TTime},
|
|
{Label: "_stop", Type: query.TTime},
|
|
{Label: "_time", Type: query.TTime},
|
|
{Label: "_value", Type: query.TFloat},
|
|
},
|
|
KeyCols: []string{"_start", "_stop"},
|
|
KeyValues: []interface{}{execute.Time(10 * time.Minute.Nanoseconds()), execute.Time(20 * time.Minute.Nanoseconds())},
|
|
Data: [][]interface{}{
|
|
{execute.Time(10 * time.Minute.Nanoseconds()), execute.Time(20 * time.Minute.Nanoseconds()), execute.Time(11 * time.Minute.Nanoseconds()), 11.0},
|
|
{execute.Time(10 * time.Minute.Nanoseconds()), execute.Time(20 * time.Minute.Nanoseconds()), execute.Time(12 * time.Minute.Nanoseconds()), 9.0},
|
|
{execute.Time(10 * time.Minute.Nanoseconds()), execute.Time(20 * time.Minute.Nanoseconds()), execute.Time(13 * time.Minute.Nanoseconds()), 1.0},
|
|
{execute.Time(10 * time.Minute.Nanoseconds()), execute.Time(20 * time.Minute.Nanoseconds()), execute.Time(14 * time.Minute.Nanoseconds()), 4.0},
|
|
},
|
|
}},
|
|
want: []*executetest.Table{{
|
|
ColMeta: []query.ColMeta{
|
|
{Label: "_start", Type: query.TTime},
|
|
{Label: "_stop", Type: query.TTime},
|
|
{Label: "_time", Type: query.TTime},
|
|
{Label: "_value", Type: query.TFloat},
|
|
},
|
|
KeyCols: []string{"_start", "_stop"},
|
|
KeyValues: []interface{}{execute.Time(10 * time.Minute.Nanoseconds()), execute.Time(20 * time.Minute.Nanoseconds())},
|
|
Data: [][]interface{}{
|
|
{execute.Time(12 * time.Minute.Nanoseconds()), execute.Time(14 * time.Minute.Nanoseconds()), execute.Time(12 * time.Minute.Nanoseconds()), 9.0},
|
|
{execute.Time(12 * time.Minute.Nanoseconds()), execute.Time(14 * time.Minute.Nanoseconds()), execute.Time(13 * time.Minute.Nanoseconds()), 1.0},
|
|
},
|
|
}},
|
|
groupKey: func() query.GroupKey {
|
|
t1, err := values.NewValue(values.Time(10*time.Minute.Nanoseconds()), semantic.Time)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t2, err := values.NewValue(values.Time(20*time.Minute.Nanoseconds()), semantic.Time)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
vs := []values.Value{t1, t2}
|
|
return execute.NewGroupKey(
|
|
[]query.ColMeta{
|
|
{Label: "_start", Type: query.TTime},
|
|
{Label: "_stop", Type: query.TTime},
|
|
},
|
|
vs,
|
|
)
|
|
},
|
|
},
|
|
}
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
if tc.groupKey != nil && tc.want != nil {
|
|
// populate group keys for the test case
|
|
for _, table := range tc.data {
|
|
tbl, ok := table.(*executetest.Table)
|
|
if !ok {
|
|
t.Fatal("failed to set group key")
|
|
}
|
|
tbl.GroupKey = tc.groupKey()
|
|
}
|
|
for _, table := range tc.want {
|
|
table.GroupKey = tc.groupKey()
|
|
}
|
|
}
|
|
executetest.ProcessTestHelper(
|
|
t,
|
|
tc.data,
|
|
tc.want,
|
|
tc.wantErr,
|
|
func(d execute.Dataset, c execute.TableBuilderCache) execute.Transformation {
|
|
var b execute.Bounds
|
|
if tc.spec.Bounds.Start.IsRelative {
|
|
b.Start = execute.Time(tc.spec.Bounds.Start.Time(tc.now.Time()).UnixNano())
|
|
} else {
|
|
b.Start = execute.Time(tc.spec.Bounds.Start.Absolute.Unix())
|
|
}
|
|
if tc.spec.Bounds.Stop.IsRelative {
|
|
b.Stop = execute.Time(tc.spec.Bounds.Stop.Time(tc.now.Time()).UnixNano())
|
|
} else {
|
|
if tc.spec.Bounds.Stop.Absolute.Unix() == 0 {
|
|
tc.spec.Bounds.Stop.Absolute = tc.now.Time()
|
|
} else {
|
|
b.Stop = execute.Time(tc.spec.Bounds.Stop.Absolute.Unix())
|
|
}
|
|
}
|
|
|
|
tr, err := functions.NewRangeTransformation(d, c, tc.spec, b)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return tr
|
|
},
|
|
)
|
|
})
|
|
}
|
|
}
|
|
func TestRange_PushDown_Duplicate(t *testing.T) {
|
|
spec := &functions.RangeProcedureSpec{
|
|
Bounds: plan.BoundsSpec{
|
|
Stop: query.Now,
|
|
},
|
|
}
|
|
root := &plan.Procedure{
|
|
Spec: &functions.FromProcedureSpec{
|
|
BoundsSet: true,
|
|
Bounds: plan.BoundsSpec{
|
|
Start: query.MinTime,
|
|
Stop: query.Now,
|
|
},
|
|
},
|
|
}
|
|
want := &plan.Procedure{
|
|
// Expect the duplicate has been reset to zero values
|
|
Spec: new(functions.FromProcedureSpec),
|
|
}
|
|
|
|
plantest.PhysicalPlan_PushDown_TestHelper(t, spec, root, true, want)
|
|
}
|
|
|
|
func TestRange_PushDown_Match(t *testing.T) {
|
|
spec := &functions.RangeProcedureSpec{
|
|
Bounds: plan.BoundsSpec{
|
|
Stop: query.Now,
|
|
},
|
|
TimeCol: "_time",
|
|
}
|
|
matchSpec := new(functions.FromProcedureSpec)
|
|
// Should match when range procedure has column `_time`
|
|
plantest.PhysicalPlan_PushDown_Match_TestHelper(t, spec, matchSpec, []bool{true})
|
|
|
|
// Should not match when range procedure column is anything else
|
|
spec.TimeCol = "_col"
|
|
plantest.PhysicalPlan_PushDown_Match_TestHelper(t, spec, matchSpec, []bool{false})
|
|
}
|