influxdb/storage/reads/array_cursor_test.go

2252 lines
58 KiB
Go

package reads
import (
"context"
"math"
"testing"
"time"
"github.com/golang/mock/gomock"
"github.com/google/go-cmp/cmp"
"github.com/influxdata/flux/interval"
"github.com/influxdata/flux/values"
"github.com/influxdata/influxdb/v2/models"
"github.com/influxdata/influxdb/v2/tsdb/cursors"
"github.com/influxdata/influxdb/v2/tsdb/cursors/mock"
"github.com/stretchr/testify/require"
)
func TestIntegerFilterArrayCursor(t *testing.T) {
var i int
expr := MockExpression{
EvalBoolFunc: func(v Valuer) bool {
i++
return i%2 == 0
},
}
var resultN int
ac := MockIntegerArrayCursor{
CloseFunc: func() {},
ErrFunc: func() error { return nil },
StatsFunc: func() cursors.CursorStats { return cursors.CursorStats{} },
NextFunc: func() *cursors.IntegerArray {
resultN++
if resultN == 4 {
return cursors.NewIntegerArrayLen(0)
}
return cursors.NewIntegerArrayLen(900)
},
}
c := newIntegerFilterArrayCursor(&expr)
c.reset(&ac)
if got, want := len(c.Next().Timestamps), 1000; got != want {
t.Fatalf("len(Next())=%d, want %d", got, want)
} else if got, want := len(c.Next().Timestamps), 350; got != want {
t.Fatalf("len(Next())=%d, want %d", got, want)
}
}
func makeIntegerArray(n int, tsStart time.Time, tsStep time.Duration, valueFn func(i int64) int64) *cursors.IntegerArray {
ia := &cursors.IntegerArray{
Timestamps: make([]int64, n),
Values: make([]int64, n),
}
for i := 0; i < n; i++ {
ia.Timestamps[i] = tsStart.UnixNano() + int64(i)*int64(tsStep)
ia.Values[i] = valueFn(int64(i))
}
return ia
}
func makeFloatArray(n int, tsStart time.Time, tsStep time.Duration, valueFn func(i int64) float64) *cursors.FloatArray {
fa := &cursors.FloatArray{
Timestamps: make([]int64, n),
Values: make([]float64, n),
}
for i := 0; i < n; i++ {
fa.Timestamps[i] = tsStart.UnixNano() + int64(i)*int64(tsStep)
fa.Values[i] = valueFn(int64(i))
}
return fa
}
func mustParseTime(ts string) time.Time {
t, err := time.Parse(time.RFC3339, ts)
if err != nil {
panic(err)
}
return t
}
func copyIntegerArray(src *cursors.IntegerArray) *cursors.IntegerArray {
dst := cursors.NewIntegerArrayLen(src.Len())
copy(dst.Timestamps, src.Timestamps)
copy(dst.Values, src.Values)
return dst
}
func copyFloatArray(src *cursors.FloatArray) *cursors.FloatArray {
dst := cursors.NewFloatArrayLen(src.Len())
copy(dst.Timestamps, src.Timestamps)
copy(dst.Values, src.Values)
return dst
}
type aggArrayCursorTest struct {
name string
createCursorFn func(cur cursors.IntegerArrayCursor, every, offset int64, window interval.Window) cursors.Cursor
every time.Duration
offset time.Duration
inputArrays []*cursors.IntegerArray
wantIntegers []*cursors.IntegerArray
wantFloats []*cursors.FloatArray
window interval.Window
}
func (a *aggArrayCursorTest) run(t *testing.T) {
t.Helper()
t.Run(a.name, func(t *testing.T) {
var resultN int
mc := &MockIntegerArrayCursor{
CloseFunc: func() {},
ErrFunc: func() error { return nil },
StatsFunc: func() cursors.CursorStats { return cursors.CursorStats{} },
NextFunc: func() *cursors.IntegerArray {
if resultN < len(a.inputArrays) {
a := a.inputArrays[resultN]
resultN++
return a
}
return &cursors.IntegerArray{}
},
}
c := a.createCursorFn(mc, int64(a.every), int64(a.offset), a.window)
switch cursor := c.(type) {
case cursors.IntegerArrayCursor:
got := make([]*cursors.IntegerArray, 0, len(a.wantIntegers))
for a := cursor.Next(); a.Len() != 0; a = cursor.Next() {
got = append(got, copyIntegerArray(a))
}
if diff := cmp.Diff(got, a.wantIntegers); diff != "" {
t.Fatalf("did not get expected result from count array cursor; -got/+want:\n%v", diff)
}
case cursors.FloatArrayCursor:
got := make([]*cursors.FloatArray, 0, len(a.wantFloats))
for a := cursor.Next(); a.Len() != 0; a = cursor.Next() {
got = append(got, copyFloatArray(a))
}
if diff := cmp.Diff(got, a.wantFloats); diff != "" {
t.Fatalf("did not get expected result from count array cursor; -got/+want:\n%v", diff)
}
default:
t.Fatalf("unsupported cursor type: %T", cursor)
}
})
}
func TestLimitArrayCursor(t *testing.T) {
arr := []*cursors.IntegerArray{
makeIntegerArray(
1000,
mustParseTime("1970-01-01T00:00:01Z"), time.Millisecond,
func(i int64) int64 { return 3 + i },
),
makeIntegerArray(
1000,
mustParseTime("1970-01-01T00:00:02Z"), time.Millisecond,
func(i int64) int64 { return 1003 + i },
),
}
idx := -1
cur := &MockIntegerArrayCursor{
CloseFunc: func() {},
ErrFunc: func() error { return nil },
StatsFunc: func() cursors.CursorStats { return cursors.CursorStats{} },
NextFunc: func() *cursors.IntegerArray {
if idx++; idx < len(arr) {
return arr[idx]
}
return &cursors.IntegerArray{}
},
}
aggCursor := newIntegerLimitArrayCursor(cur)
want := []*cursors.IntegerArray{
{
Timestamps: []int64{mustParseTime("1970-01-01T00:00:01Z").UnixNano()},
Values: []int64{3},
},
}
got := []*cursors.IntegerArray{}
for a := aggCursor.Next(); a.Len() != 0; a = aggCursor.Next() {
got = append(got, a)
}
if !cmp.Equal(want, got) {
t.Fatalf("unexpected result; -want/+got:\n%v", cmp.Diff(want, got))
}
}
func TestWindowFirstArrayCursor(t *testing.T) {
testcases := []aggArrayCursorTest{
{
name: "window",
every: 15 * time.Minute,
inputArrays: []*cursors.IntegerArray{
makeIntegerArray(
60,
mustParseTime("2010-01-01T00:00:00Z"), time.Minute,
func(i int64) int64 { return i },
),
},
wantIntegers: []*cursors.IntegerArray{
makeIntegerArray(
4,
mustParseTime("2010-01-01T00:00:00Z"), 15*time.Minute,
func(i int64) int64 { return 15 * i },
),
},
},
{
name: "offset window",
every: 15 * time.Minute,
offset: time.Minute,
inputArrays: []*cursors.IntegerArray{
makeIntegerArray(
60,
mustParseTime("2010-01-01T00:00:00Z"), time.Minute,
func(i int64) int64 { return i },
),
},
wantIntegers: []*cursors.IntegerArray{
{
Timestamps: []int64{
mustParseTime("2010-01-01T00:00:00Z").UnixNano(),
mustParseTime("2010-01-01T00:01:00Z").UnixNano(),
mustParseTime("2010-01-01T00:16:00Z").UnixNano(),
mustParseTime("2010-01-01T00:31:00Z").UnixNano(),
mustParseTime("2010-01-01T00:46:00Z").UnixNano(),
},
Values: []int64{0, 1, 16, 31, 46},
},
},
},
{
name: "empty windows",
every: time.Minute,
inputArrays: []*cursors.IntegerArray{
makeIntegerArray(
4,
mustParseTime("2010-01-01T00:00:00Z"), 15*time.Minute,
func(i int64) int64 { return i },
),
},
wantIntegers: []*cursors.IntegerArray{
makeIntegerArray(
4,
mustParseTime("2010-01-01T00:00:00Z"), 15*time.Minute,
func(i int64) int64 { return i },
),
},
},
{
name: "empty offset windows",
every: time.Minute,
offset: time.Minute,
inputArrays: []*cursors.IntegerArray{
makeIntegerArray(
4,
mustParseTime("2010-01-01T00:00:00Z"), 15*time.Minute,
func(i int64) int64 { return i },
),
},
wantIntegers: []*cursors.IntegerArray{
makeIntegerArray(
4,
mustParseTime("2010-01-01T00:00:00Z"), 15*time.Minute,
func(i int64) int64 { return i },
),
},
},
{
name: "unaligned window",
every: 15 * time.Minute,
inputArrays: []*cursors.IntegerArray{
makeIntegerArray(
60,
mustParseTime("2010-01-01T00:00:30Z"), time.Minute,
func(i int64) int64 { return i },
),
},
wantIntegers: []*cursors.IntegerArray{
makeIntegerArray(
4,
mustParseTime("2010-01-01T00:00:30Z"), 15*time.Minute,
func(i int64) int64 { return 15 * i },
),
},
},
{
name: "unaligned offset window",
every: 15 * time.Minute,
offset: 45 * time.Second,
inputArrays: []*cursors.IntegerArray{
makeIntegerArray(
60,
mustParseTime("2010-01-01T00:00:30Z"), time.Minute,
func(i int64) int64 { return i },
),
},
wantIntegers: []*cursors.IntegerArray{
{
Timestamps: []int64{
mustParseTime("2010-01-01T00:00:30Z").UnixNano(),
mustParseTime("2010-01-01T00:01:30Z").UnixNano(),
mustParseTime("2010-01-01T00:16:30Z").UnixNano(),
mustParseTime("2010-01-01T00:31:30Z").UnixNano(),
mustParseTime("2010-01-01T00:46:30Z").UnixNano(),
},
Values: []int64{0, 1, 16, 31, 46},
},
},
},
{
name: "more unaligned window",
every: 15 * time.Minute,
inputArrays: []*cursors.IntegerArray{
makeIntegerArray(
60,
mustParseTime("2010-01-01T00:01:30Z"), time.Minute,
func(i int64) int64 { return i },
),
},
wantIntegers: []*cursors.IntegerArray{
{
Timestamps: []int64{
mustParseTime("2010-01-01T00:01:30Z").UnixNano(),
mustParseTime("2010-01-01T00:15:30Z").UnixNano(),
mustParseTime("2010-01-01T00:30:30Z").UnixNano(),
mustParseTime("2010-01-01T00:45:30Z").UnixNano(),
mustParseTime("2010-01-01T01:00:30Z").UnixNano(),
},
Values: []int64{0, 14, 29, 44, 59},
},
},
},
{
name: "window two input arrays",
every: 15 * time.Minute,
inputArrays: []*cursors.IntegerArray{
makeIntegerArray(
60,
mustParseTime("2010-01-01T00:00:00Z"), time.Minute,
func(i int64) int64 { return i },
),
makeIntegerArray(
60,
mustParseTime("2010-01-01T01:00:00Z"), time.Minute,
func(i int64) int64 { return 60 + i },
),
},
wantIntegers: []*cursors.IntegerArray{
makeIntegerArray(
8,
mustParseTime("2010-01-01T00:00:00Z"), 15*time.Minute,
func(i int64) int64 { return 15 * i },
),
},
},
{
name: "offset window two input arrays",
every: 30 * time.Minute,
offset: 27 * time.Minute,
inputArrays: []*cursors.IntegerArray{
makeIntegerArray(
60,
mustParseTime("2010-01-01T00:00:00Z"), time.Minute,
func(i int64) int64 { return i },
),
makeIntegerArray(
60,
mustParseTime("2010-01-01T01:00:00Z"), time.Minute,
func(i int64) int64 { return 60 + i },
),
},
wantIntegers: []*cursors.IntegerArray{
{
Timestamps: []int64{
mustParseTime("2010-01-01T00:00:00Z").UnixNano(),
mustParseTime("2010-01-01T00:27:00Z").UnixNano(),
mustParseTime("2010-01-01T00:57:00Z").UnixNano(),
mustParseTime("2010-01-01T01:27:00Z").UnixNano(),
mustParseTime("2010-01-01T01:57:00Z").UnixNano(),
},
Values: []int64{0, 27, 57, 87, 117},
},
},
},
{
name: "window spans input arrays",
every: 40 * time.Minute,
inputArrays: []*cursors.IntegerArray{
makeIntegerArray(
60,
mustParseTime("2010-01-01T00:00:00Z"), time.Minute,
func(i int64) int64 { return i },
),
makeIntegerArray(
60,
mustParseTime("2010-01-01T01:00:00Z"), time.Minute,
func(i int64) int64 { return 60 + i },
),
},
wantIntegers: []*cursors.IntegerArray{
makeIntegerArray(
3,
mustParseTime("2010-01-01T00:00:00Z"), 40*time.Minute,
func(i int64) int64 { return 40 * i },
),
},
},
{
name: "offset window spans input arrays",
every: 40 * time.Minute,
offset: 10 * time.Minute,
inputArrays: []*cursors.IntegerArray{
makeIntegerArray(
60,
mustParseTime("2010-01-01T00:00:00Z"), time.Minute,
func(i int64) int64 { return i },
),
makeIntegerArray(
60,
mustParseTime("2010-01-01T01:00:00Z"), time.Minute,
func(i int64) int64 { return 60 + i },
),
},
wantIntegers: []*cursors.IntegerArray{
{
Timestamps: []int64{
mustParseTime("2010-01-01T00:00:00Z").UnixNano(),
mustParseTime("2010-01-01T00:10:00Z").UnixNano(),
mustParseTime("2010-01-01T00:50:00Z").UnixNano(),
mustParseTime("2010-01-01T01:30:00Z").UnixNano(),
},
Values: []int64{0, 10, 50, 90},
},
},
},
{
name: "more windows than MaxPointsPerBlock",
every: 2 * time.Millisecond,
inputArrays: []*cursors.IntegerArray{
makeIntegerArray( // 1 second, one point per ms
1000,
mustParseTime("2010-01-01T00:00:00Z"), time.Millisecond,
func(i int64) int64 { return i },
),
makeIntegerArray( // 1 second, one point per ms
1000,
mustParseTime("2010-01-01T00:00:01Z"), time.Millisecond,
func(i int64) int64 { return 1000 + i },
),
makeIntegerArray( // 1 second, one point per ms
1000,
mustParseTime("2010-01-01T00:00:02Z"), time.Millisecond,
func(i int64) int64 { return 2000 + i },
),
},
wantIntegers: []*cursors.IntegerArray{
makeIntegerArray(
1000,
mustParseTime("2010-01-01T00:00:00.000Z"), 2*time.Millisecond,
func(i int64) int64 { return 2 * i },
),
makeIntegerArray(
500,
mustParseTime("2010-01-01T00:00:02.000Z"), 2*time.Millisecond,
func(i int64) int64 { return 2000 + 2*i },
),
},
},
{
name: "more offset windows than MaxPointsPerBlock",
every: 2 * time.Millisecond,
offset: 1 * time.Millisecond,
inputArrays: []*cursors.IntegerArray{
makeIntegerArray( // 1 second, one point per ms
1000,
mustParseTime("2010-01-01T00:00:00Z"), time.Millisecond,
func(i int64) int64 { return i },
),
makeIntegerArray( // 1 second, one point per ms
1000,
mustParseTime("2010-01-01T00:00:01Z"), time.Millisecond,
func(i int64) int64 { return 1000 + i },
),
makeIntegerArray( // 1 second, one point per ms
1000,
mustParseTime("2010-01-01T00:00:02Z"), time.Millisecond,
func(i int64) int64 { return 2000 + i },
),
},
wantIntegers: []*cursors.IntegerArray{
func() *cursors.IntegerArray {
arr := makeIntegerArray(
999,
mustParseTime("2010-01-01T00:00:00.001Z"), 2*time.Millisecond,
func(i int64) int64 { return 1 + 2*i },
)
return &cursors.IntegerArray{
Timestamps: append([]int64{mustParseTime("2010-01-01T00:00:00.000Z").UnixNano()}, arr.Timestamps...),
Values: append([]int64{0}, arr.Values...),
}
}(),
makeIntegerArray(
501,
mustParseTime("2010-01-01T00:00:01.999Z"), 2*time.Millisecond,
func(i int64) int64 { return 1999 + 2*i },
),
},
},
{
name: "whole series",
inputArrays: []*cursors.IntegerArray{
makeIntegerArray(
60,
mustParseTime("2010-01-01T00:00:00Z"), time.Minute,
func(i int64) int64 { return 100 + i },
),
},
wantIntegers: []*cursors.IntegerArray{
makeIntegerArray(
1,
mustParseTime("2010-01-01T00:00:00Z"), time.Minute,
func(int64) int64 { return 100 },
),
},
},
{
name: "whole series no points",
inputArrays: []*cursors.IntegerArray{{}},
wantIntegers: []*cursors.IntegerArray{},
},
{
name: "whole series two arrays",
inputArrays: []*cursors.IntegerArray{
makeIntegerArray(
60,
mustParseTime("2010-01-01T00:00:00Z"), time.Minute,
func(i int64) int64 { return 10 + i },
),
makeIntegerArray(
60,
mustParseTime("2010-01-01T01:00:00Z"), time.Minute,
func(i int64) int64 { return 70 + i },
),
},
wantIntegers: []*cursors.IntegerArray{
makeIntegerArray(
1,
mustParseTime("2010-01-01T00:00:00Z"), time.Minute,
func(int64) int64 { return 10 },
),
},
},
{
name: "whole series span epoch",
inputArrays: []*cursors.IntegerArray{
makeIntegerArray(
120,
mustParseTime("1969-12-31T23:00:00Z"), time.Minute,
func(i int64) int64 { return 100 + i },
),
},
wantIntegers: []*cursors.IntegerArray{
makeIntegerArray(
1,
mustParseTime("1969-12-31T23:00:00Z"), time.Minute,
func(int64) int64 { return 100 },
),
},
},
{
name: "whole series span epoch two arrays",
inputArrays: []*cursors.IntegerArray{
makeIntegerArray(
60,
mustParseTime("1969-12-31T23:00:00Z"), time.Minute,
func(i int64) int64 { return 100 + i },
),
makeIntegerArray(
60,
mustParseTime("1970-01-01T00:00:00Z"), time.Minute,
func(i int64) int64 { return 160 + i },
),
},
wantIntegers: []*cursors.IntegerArray{
makeIntegerArray(
1,
mustParseTime("1969-12-31T23:00:00Z"), time.Minute,
func(int64) int64 { return 100 },
),
},
},
{
name: "whole series, with max int64 timestamp",
inputArrays: []*cursors.IntegerArray{
{
Timestamps: []int64{math.MaxInt64},
Values: []int64{12},
},
},
wantIntegers: []*cursors.IntegerArray{
{
Timestamps: []int64{math.MaxInt64},
Values: []int64{12},
},
},
},
}
for _, tc := range testcases {
tc.createCursorFn = func(cur cursors.IntegerArrayCursor, every, offset int64, window interval.Window) cursors.Cursor {
if every == 0 {
if window.IsZero() {
return newIntegerLimitArrayCursor(cur)
}
}
// if either the every or offset are set, then create a window for nsec values
// every and window.Every should never BOTH be zero here
if every != 0 || offset != 0 {
window, _ = interval.NewWindow(
values.MakeDuration(every, 0, false),
values.MakeDuration(every, 0, false),
values.MakeDuration(offset, 0, false),
)
}
// otherwise just use the window that was passed in
return newIntegerWindowFirstArrayCursor(cur, window)
}
tc.run(t)
}
}
func TestWindowLastArrayCursor(t *testing.T) {
testcases := []aggArrayCursorTest{
{
name: "window",
every: 15 * time.Minute,
inputArrays: []*cursors.IntegerArray{
makeIntegerArray(
60,
mustParseTime("2010-01-01T00:00:00Z"), time.Minute,
func(i int64) int64 { return i },
),
},
wantIntegers: []*cursors.IntegerArray{
makeIntegerArray(
4,
mustParseTime("2010-01-01T00:14:00Z"), 15*time.Minute,
func(i int64) int64 { return 14 + 15*i },
),
},
},
{
name: "empty windows",
every: time.Minute,
inputArrays: []*cursors.IntegerArray{
makeIntegerArray(
4,
mustParseTime("2010-01-01T00:00:00Z"), 15*time.Minute,
func(i int64) int64 { return i },
),
},
wantIntegers: []*cursors.IntegerArray{
makeIntegerArray(
4,
mustParseTime("2010-01-01T00:00:00Z"), 15*time.Minute,
func(i int64) int64 { return i },
),
},
},
{
name: "unaligned window",
every: 15 * time.Minute,
inputArrays: []*cursors.IntegerArray{
makeIntegerArray(
60,
mustParseTime("2010-01-01T00:00:30Z"), time.Minute,
func(i int64) int64 { return i },
),
},
wantIntegers: []*cursors.IntegerArray{
makeIntegerArray(
4,
mustParseTime("2010-01-01T00:14:30Z"), 15*time.Minute,
func(i int64) int64 { return 14 + 15*i },
),
},
},
{
name: "more unaligned window",
every: 15 * time.Minute,
inputArrays: []*cursors.IntegerArray{
makeIntegerArray(
60,
mustParseTime("2010-01-01T00:01:30Z"), time.Minute,
func(i int64) int64 { return i },
),
},
wantIntegers: []*cursors.IntegerArray{
{
Timestamps: []int64{
mustParseTime("2010-01-01T00:14:30Z").UnixNano(),
mustParseTime("2010-01-01T00:29:30Z").UnixNano(),
mustParseTime("2010-01-01T00:44:30Z").UnixNano(),
mustParseTime("2010-01-01T00:59:30Z").UnixNano(),
mustParseTime("2010-01-01T01:00:30Z").UnixNano(),
},
Values: []int64{13, 28, 43, 58, 59},
},
},
},
{
name: "window two input arrays",
every: 15 * time.Minute,
inputArrays: []*cursors.IntegerArray{
makeIntegerArray(
60,
mustParseTime("2010-01-01T00:00:00Z"), time.Minute,
func(i int64) int64 { return i },
),
makeIntegerArray(
60,
mustParseTime("2010-01-01T01:00:00Z"), time.Minute,
func(i int64) int64 { return 60 + i },
),
},
wantIntegers: []*cursors.IntegerArray{
makeIntegerArray(
8,
mustParseTime("2010-01-01T00:14:00Z"), 15*time.Minute,
func(i int64) int64 { return 14 + 15*i },
),
},
},
{
name: "window spans input arrays",
every: 40 * time.Minute,
inputArrays: []*cursors.IntegerArray{
makeIntegerArray(
60,
mustParseTime("2010-01-01T00:00:00Z"), time.Minute,
func(i int64) int64 { return i },
),
makeIntegerArray(
60,
mustParseTime("2010-01-01T01:00:00Z"), time.Minute,
func(i int64) int64 { return 60 + i },
),
},
wantIntegers: []*cursors.IntegerArray{
makeIntegerArray(
3,
mustParseTime("2010-01-01T00:39:00Z"), 40*time.Minute,
func(i int64) int64 { return 39 + 40*i },
),
},
},
{
name: "more windows than MaxPointsPerBlock",
every: 2 * time.Millisecond,
inputArrays: []*cursors.IntegerArray{
makeIntegerArray( // 1 second, one point per ms
1000,
mustParseTime("2010-01-01T00:00:00Z"), time.Millisecond,
func(i int64) int64 { return i },
),
makeIntegerArray( // 1 second, one point per ms
1000,
mustParseTime("2010-01-01T00:00:01Z"), time.Millisecond,
func(i int64) int64 { return 1000 + i },
),
makeIntegerArray( // 1 second, one point per ms
1000,
mustParseTime("2010-01-01T00:00:02Z"), time.Millisecond,
func(i int64) int64 { return 2000 + i },
),
},
wantIntegers: []*cursors.IntegerArray{
makeIntegerArray(
1000,
mustParseTime("2010-01-01T00:00:00.001Z"), 2*time.Millisecond,
func(i int64) int64 { return 1 + 2*i },
),
makeIntegerArray(
500,
mustParseTime("2010-01-01T00:00:02.001Z"), 2*time.Millisecond,
func(i int64) int64 { return 2001 + 2*i },
),
},
},
{
name: "MaxPointsPerBlock",
every: time.Millisecond,
inputArrays: []*cursors.IntegerArray{
makeIntegerArray( // 1 second, one point per ms
1000,
mustParseTime("2010-01-01T00:00:00Z"), time.Millisecond,
func(i int64) int64 { return i },
),
makeIntegerArray( // 1 second, one point per ms
1000,
mustParseTime("2010-01-01T00:00:01Z"), time.Millisecond,
func(i int64) int64 { return 1000 + i },
),
},
wantIntegers: []*cursors.IntegerArray{
makeIntegerArray(
1000,
mustParseTime("2010-01-01T00:00:00Z"), time.Millisecond,
func(i int64) int64 { return i },
),
makeIntegerArray(
1000,
mustParseTime("2010-01-01T00:00:01Z"), time.Millisecond,
func(i int64) int64 { return 1000 + i },
),
},
},
}
for _, tc := range testcases {
tc.createCursorFn = func(cur cursors.IntegerArrayCursor, every, offset int64, window interval.Window) cursors.Cursor {
if every != 0 || offset != 0 {
window, _ = interval.NewWindow(
values.MakeDuration(every, 0, false),
values.MakeDuration(every, 0, false),
values.MakeDuration(offset, 0, false),
)
}
return newIntegerWindowLastArrayCursor(cur, window)
}
tc.run(t)
}
}
func TestIntegerCountArrayCursor(t *testing.T) {
maxTimestamp := time.Unix(0, math.MaxInt64)
testcases := []aggArrayCursorTest{
{
name: "window",
every: 15 * time.Minute,
inputArrays: []*cursors.IntegerArray{
makeIntegerArray(
60,
mustParseTime("2010-01-01T00:00:00Z"), time.Minute,
func(i int64) int64 { return 100 + i },
),
},
wantIntegers: []*cursors.IntegerArray{
makeIntegerArray(4, mustParseTime("2010-01-01T00:15:00Z"), 15*time.Minute, func(int64) int64 { return 15 }),
},
},
{
name: "offset window",
every: 15 * time.Minute,
offset: time.Minute,
inputArrays: []*cursors.IntegerArray{
makeIntegerArray(
60,
mustParseTime("2010-01-01T00:00:00Z"), time.Minute,
func(i int64) int64 { return i },
),
},
wantIntegers: []*cursors.IntegerArray{
makeIntegerArray(5, mustParseTime("2010-01-01T00:01:00Z"), 15*time.Minute, func(i int64) int64 {
switch i {
case 0:
return 1
case 4:
return 14
default:
return 15
}
}),
},
},
{
name: "empty windows",
every: time.Minute,
inputArrays: []*cursors.IntegerArray{
makeIntegerArray(
4,
mustParseTime("2010-01-01T00:00:00Z"), 15*time.Minute,
func(i int64) int64 { return 100 + i },
),
},
wantIntegers: []*cursors.IntegerArray{
makeIntegerArray(
4,
mustParseTime("2010-01-01T00:01:00Z"), 15*time.Minute,
func(i int64) int64 { return 1 },
),
},
},
{
name: "empty offset windows",
every: time.Minute,
offset: time.Minute,
inputArrays: []*cursors.IntegerArray{
makeIntegerArray(
4,
mustParseTime("2010-01-01T00:00:00Z"), 15*time.Minute,
func(i int64) int64 { return 100 + i },
),
},
wantIntegers: []*cursors.IntegerArray{
makeIntegerArray(
4,
mustParseTime("2010-01-01T00:01:00Z"), 15*time.Minute,
func(int64) int64 { return 1 },
),
},
},
{
name: "unaligned window",
every: 15 * time.Minute,
inputArrays: []*cursors.IntegerArray{
makeIntegerArray(
60,
mustParseTime("2010-01-01T00:00:30Z"), time.Minute,
func(i int64) int64 { return 100 + i },
),
},
wantIntegers: []*cursors.IntegerArray{
makeIntegerArray(
4,
mustParseTime("2010-01-01T00:15:00Z"), 15*time.Minute,
func(i int64) int64 {
return 15
}),
},
},
{
name: "unaligned offset window",
every: 15 * time.Minute,
offset: 45 * time.Second,
inputArrays: []*cursors.IntegerArray{
makeIntegerArray(
60,
mustParseTime("2010-01-01T00:00:30Z"), time.Minute,
func(i int64) int64 { return i },
),
},
wantIntegers: []*cursors.IntegerArray{
makeIntegerArray(
5,
mustParseTime("2010-01-01T00:00:45Z"), 15*time.Minute,
func(i int64) int64 {
switch i {
case 0:
return 1
case 4:
return 14
default:
return 15
}
}),
},
},
{
name: "more unaligned window",
every: 15 * time.Minute,
inputArrays: []*cursors.IntegerArray{
makeIntegerArray(
60,
mustParseTime("2010-01-01T00:01:30Z"), time.Minute,
func(i int64) int64 { return 100 + i },
),
},
wantIntegers: []*cursors.IntegerArray{
makeIntegerArray(
5,
mustParseTime("2010-01-01T00:15:00Z"), 15*time.Minute,
func(i int64) int64 {
switch i {
case 0:
return 14
case 4:
return 1
default:
return 15
}
}),
},
},
{
name: "window two input arrays",
every: 15 * time.Minute,
inputArrays: []*cursors.IntegerArray{
makeIntegerArray(
60,
mustParseTime("2010-01-01T00:00:00Z"), time.Minute,
func(i int64) int64 { return 100 + i },
),
makeIntegerArray(
60,
mustParseTime("2010-01-01T01:00:00Z"), time.Minute,
func(i int64) int64 { return 200 + i },
),
},
wantIntegers: []*cursors.IntegerArray{
makeIntegerArray(8, mustParseTime("2010-01-01T00:15:00Z"), 15*time.Minute, func(int64) int64 { return 15 }),
},
},
{
name: "offset window two input arrays",
every: 30 * time.Minute,
offset: 27 * time.Minute,
inputArrays: []*cursors.IntegerArray{
makeIntegerArray(
60,
mustParseTime("2010-01-01T00:00:00Z"), time.Minute,
func(i int64) int64 { return i },
),
makeIntegerArray(
60,
mustParseTime("2010-01-01T01:00:00Z"), time.Minute,
func(i int64) int64 { return 60 + i },
),
},
wantIntegers: []*cursors.IntegerArray{
makeIntegerArray(5, mustParseTime("2010-01-01T00:27:00Z"), 30*time.Minute, func(i int64) int64 {
switch i {
case 0:
return 27
case 4:
return 3
default:
return 30
}
}),
},
},
{
name: "window spans input arrays",
every: 40 * time.Minute,
inputArrays: []*cursors.IntegerArray{
makeIntegerArray(
60,
mustParseTime("2010-01-01T00:00:00Z"), time.Minute,
func(i int64) int64 { return 100 + i },
),
makeIntegerArray(
60,
mustParseTime("2010-01-01T01:00:00Z"), time.Minute,
func(i int64) int64 { return 200 + i },
),
},
wantIntegers: []*cursors.IntegerArray{
makeIntegerArray(3, mustParseTime("2010-01-01T00:40:00Z"), 40*time.Minute, func(int64) int64 { return 40 }),
},
},
{
name: "offset window spans input arrays",
every: 40 * time.Minute,
offset: 10 * time.Minute,
inputArrays: []*cursors.IntegerArray{
makeIntegerArray(
60,
mustParseTime("2010-01-01T00:00:00Z"), time.Minute,
func(i int64) int64 { return i },
),
makeIntegerArray(
60,
mustParseTime("2010-01-01T01:00:00Z"), time.Minute,
func(i int64) int64 { return 60 + i },
),
},
wantIntegers: []*cursors.IntegerArray{
makeIntegerArray(4, mustParseTime("2010-01-01T00:10:00Z"), 40*time.Minute, func(i int64) int64 {
switch i {
case 0:
return 10
case 3:
return 30
default:
return 40
}
}),
},
},
{
name: "more windows than MaxPointsPerBlock",
every: 2 * time.Millisecond,
inputArrays: []*cursors.IntegerArray{
makeIntegerArray( // 1 second, one point per ms
1000,
mustParseTime("2010-01-01T00:00:00Z"), time.Millisecond,
func(i int64) int64 { return i },
),
makeIntegerArray( // 1 second, one point per ms
1000,
mustParseTime("2010-01-01T00:00:01Z"), time.Millisecond,
func(i int64) int64 { return i },
),
makeIntegerArray( // 1 second, one point per ms
1000,
mustParseTime("2010-01-01T00:00:02Z"), time.Millisecond,
func(i int64) int64 { return i },
),
},
wantIntegers: []*cursors.IntegerArray{
makeIntegerArray(
1000,
mustParseTime("2010-01-01T00:00:00.002Z"), 2*time.Millisecond,
func(i int64) int64 { return 2 },
),
makeIntegerArray(
500,
mustParseTime("2010-01-01T00:00:02.002Z"), 2*time.Millisecond,
func(i int64) int64 { return 2 },
),
},
},
{
name: "more offset windows than MaxPointsPerBlock",
every: 2 * time.Millisecond,
offset: 1 * time.Millisecond,
inputArrays: []*cursors.IntegerArray{
makeIntegerArray( // 1 second, one point per ms
1000,
mustParseTime("2010-01-01T00:00:00Z"), time.Millisecond,
func(i int64) int64 { return i },
),
makeIntegerArray( // 1 second, one point per ms
1000,
mustParseTime("2010-01-01T00:00:01Z"), time.Millisecond,
func(i int64) int64 { return 1000 + i },
),
makeIntegerArray( // 1 second, one point per ms
1000,
mustParseTime("2010-01-01T00:00:02Z"), time.Millisecond,
func(i int64) int64 { return 2000 + i },
),
},
wantIntegers: []*cursors.IntegerArray{
makeIntegerArray(
1000,
mustParseTime("2010-01-01T00:00:00.001Z"), 2*time.Millisecond,
func(i int64) int64 {
switch i {
case 0:
return 1
default:
return 2
}
},
),
makeIntegerArray(
501,
mustParseTime("2010-01-01T00:00:02.001Z"), 2*time.Millisecond,
func(i int64) int64 {
switch i {
case 500:
return 1
default:
return 2
}
},
),
},
},
{
name: "whole series",
inputArrays: []*cursors.IntegerArray{
makeIntegerArray(
60,
mustParseTime("2010-01-01T00:00:00Z"), time.Minute,
func(i int64) int64 { return 100 + i },
),
},
wantIntegers: []*cursors.IntegerArray{
makeIntegerArray(1, maxTimestamp, 40*time.Minute, func(i int64) int64 { return 60 }),
},
},
{
name: "whole series no points",
inputArrays: []*cursors.IntegerArray{{}},
wantIntegers: []*cursors.IntegerArray{},
},
{
name: "whole series two arrays",
inputArrays: []*cursors.IntegerArray{
makeIntegerArray(
60,
mustParseTime("2010-01-01T00:00:00Z"), time.Minute,
func(i int64) int64 { return 100 + i },
),
makeIntegerArray(
60,
mustParseTime("2010-01-01T01:00:00Z"), time.Minute,
func(i int64) int64 { return 100 + i },
),
},
wantIntegers: []*cursors.IntegerArray{
makeIntegerArray(1, maxTimestamp, 40*time.Minute, func(int64) int64 { return 120 }),
},
},
{
name: "whole series span epoch",
inputArrays: []*cursors.IntegerArray{
makeIntegerArray(
120,
mustParseTime("1969-12-31T23:00:00Z"), time.Minute,
func(i int64) int64 { return 100 + i },
),
},
wantIntegers: []*cursors.IntegerArray{
makeIntegerArray(1, maxTimestamp, 40*time.Minute, func(int64) int64 { return 120 }),
},
},
{
name: "whole series span epoch two arrays",
inputArrays: []*cursors.IntegerArray{
makeIntegerArray(
60,
mustParseTime("1969-12-31T23:00:00Z"), time.Minute,
func(i int64) int64 { return 100 + i },
),
makeIntegerArray(
60,
mustParseTime("1970-01-01T00:00:00Z"), time.Minute,
func(i int64) int64 { return 100 + i },
),
},
wantIntegers: []*cursors.IntegerArray{
makeIntegerArray(1, maxTimestamp, 40*time.Minute, func(int64) int64 { return 120 }),
},
},
{
name: "whole series, with max int64 timestamp",
inputArrays: []*cursors.IntegerArray{
{
Timestamps: []int64{math.MaxInt64},
Values: []int64{0},
},
},
wantIntegers: []*cursors.IntegerArray{
{
Timestamps: []int64{math.MaxInt64},
Values: []int64{1},
},
},
},
{
name: "monthly spans multiple periods",
window: func() interval.Window {
window, _ := interval.NewWindow(
values.MakeDuration(0, 1, false),
values.MakeDuration(0, 1, false),
values.MakeDuration(0, 0, false),
)
return window
}(),
inputArrays: []*cursors.IntegerArray{
makeIntegerArray(
60,
mustParseTime("2010-01-01T00:00:00Z"), time.Minute,
func(i int64) int64 { return 100 + i },
),
makeIntegerArray(
60,
mustParseTime("2010-02-01T00:00:00Z"), time.Minute,
func(i int64) int64 { return 100 + i },
),
},
wantIntegers: []*cursors.IntegerArray{
makeIntegerArray(2, mustParseTime("2010-02-01T00:00:00Z"), 2419200000000000, func(int64) int64 { return 60 }),
},
},
{
name: "monthly window w/ offset",
window: func() interval.Window {
window, _ := interval.NewWindow(
values.MakeDuration(0, 1, false),
values.MakeDuration(0, 1, false),
values.MakeDuration(1209600000000000, 0, false),
)
return window
}(),
inputArrays: []*cursors.IntegerArray{
makeIntegerArray(
60,
mustParseTime("2010-01-01T00:00:00Z"), time.Minute,
func(i int64) int64 { return 100 + i },
),
},
wantIntegers: []*cursors.IntegerArray{
makeIntegerArray(1, mustParseTime("2010-01-15T00:00:00Z"), 0, func(int64) int64 { return 60 }),
},
},
{
name: "monthly windows",
window: func() interval.Window {
window, _ := interval.NewWindow(
values.MakeDuration(0, 1, false),
values.MakeDuration(0, 1, false),
values.MakeDuration(0, 0, false),
)
return window
}(),
inputArrays: []*cursors.IntegerArray{
makeIntegerArray(
60,
mustParseTime("2010-01-01T00:00:00Z"), time.Minute,
func(i int64) int64 { return 100 + i },
),
},
wantIntegers: []*cursors.IntegerArray{
makeIntegerArray(1, mustParseTime("2010-02-01T00:00:00Z"), 0, func(int64) int64 { return 60 }),
},
},
}
for _, tc := range testcases {
tc.createCursorFn = func(cur cursors.IntegerArrayCursor, every, offset int64, window interval.Window) cursors.Cursor {
if every != 0 || offset != 0 {
window, _ = interval.NewWindow(
values.MakeDuration(every, 0, false),
values.MakeDuration(every, 0, false),
values.MakeDuration(offset, 0, false),
)
}
return newIntegerWindowCountArrayCursor(cur, window)
}
tc.run(t)
}
}
func TestIntegerSumArrayCursor(t *testing.T) {
maxTimestamp := time.Unix(0, math.MaxInt64)
testcases := []aggArrayCursorTest{
{
name: "window",
every: 15 * time.Minute,
inputArrays: []*cursors.IntegerArray{
makeIntegerArray(
60,
mustParseTime("2010-01-01T00:00:00Z"), time.Minute,
func(i int64) int64 { return 2 },
),
},
wantIntegers: []*cursors.IntegerArray{
makeIntegerArray(4, mustParseTime("2010-01-01T00:15:00Z"), 15*time.Minute, func(int64) int64 { return 30 }),
},
},
{
name: "empty windows",
every: time.Minute,
inputArrays: []*cursors.IntegerArray{
makeIntegerArray(
4,
mustParseTime("2010-01-01T00:00:00Z"), 15*time.Minute,
func(i int64) int64 { return 100 + i },
),
},
wantIntegers: []*cursors.IntegerArray{
makeIntegerArray(
4,
mustParseTime("2010-01-01T00:01:00Z"), 15*time.Minute,
func(i int64) int64 { return 100 + i },
),
},
},
{
name: "unaligned window",
every: 15 * time.Minute,
inputArrays: []*cursors.IntegerArray{
makeIntegerArray(
60,
mustParseTime("2010-01-01T00:00:30Z"), time.Minute,
func(i int64) int64 { return 2 },
),
},
wantIntegers: []*cursors.IntegerArray{
makeIntegerArray(
4,
mustParseTime("2010-01-01T00:15:00Z"), 15*time.Minute,
func(i int64) int64 {
return 30
}),
},
},
{
name: "more unaligned window",
every: 15 * time.Minute,
inputArrays: []*cursors.IntegerArray{
makeIntegerArray(
60,
mustParseTime("2010-01-01T00:01:30Z"), time.Minute,
func(i int64) int64 { return 2 },
),
},
wantIntegers: []*cursors.IntegerArray{
makeIntegerArray(
5,
mustParseTime("2010-01-01T00:15:00Z"), 15*time.Minute,
func(i int64) int64 {
switch i {
case 0:
return 28
case 4:
return 2
default:
return 30
}
}),
},
},
{
name: "window two input arrays",
every: 15 * time.Minute,
inputArrays: []*cursors.IntegerArray{
makeIntegerArray(
60,
mustParseTime("2010-01-01T00:00:00Z"), time.Minute,
func(i int64) int64 { return 2 },
),
makeIntegerArray(
60,
mustParseTime("2010-01-01T01:00:00Z"), time.Minute,
func(i int64) int64 { return 3 },
),
},
wantIntegers: []*cursors.IntegerArray{
makeIntegerArray(8, mustParseTime("2010-01-01T00:15:00Z"), 15*time.Minute,
func(i int64) int64 {
if i < 4 {
return 30
} else {
return 45
}
}),
},
},
{
name: "window spans input arrays",
every: 40 * time.Minute,
inputArrays: []*cursors.IntegerArray{
makeIntegerArray(
60,
mustParseTime("2010-01-01T00:00:00Z"), time.Minute,
func(i int64) int64 { return 2 },
),
makeIntegerArray(
60,
mustParseTime("2010-01-01T01:00:00Z"), time.Minute,
func(i int64) int64 { return 3 },
),
},
wantIntegers: []*cursors.IntegerArray{
makeIntegerArray(3, mustParseTime("2010-01-01T00:40:00Z"), 40*time.Minute,
func(i int64) int64 {
switch i {
case 0:
return 80
case 1:
return 100
case 2:
return 120
}
return -1
}),
},
},
{
name: "more windows than MaxPointsPerBlock",
every: 2 * time.Millisecond,
inputArrays: []*cursors.IntegerArray{
makeIntegerArray( // 1 second, one point per ms
1000,
mustParseTime("2010-01-01T00:00:00Z"), time.Millisecond,
func(i int64) int64 { return 2 },
),
makeIntegerArray( // 1 second, one point per ms
1000,
mustParseTime("2010-01-01T00:00:01Z"), time.Millisecond,
func(i int64) int64 { return 3 },
),
makeIntegerArray( // 1 second, one point per ms
1000,
mustParseTime("2010-01-01T00:00:02Z"), time.Millisecond,
func(i int64) int64 { return 4 },
),
},
wantIntegers: []*cursors.IntegerArray{
makeIntegerArray(
1000,
mustParseTime("2010-01-01T00:00:00.002Z"), 2*time.Millisecond,
func(i int64) int64 {
if i < 500 {
return 4
} else {
return 6
}
},
),
makeIntegerArray(
500,
mustParseTime("2010-01-01T00:00:02.002Z"), 2*time.Millisecond,
func(i int64) int64 { return 8 },
),
},
},
{
name: "whole series",
inputArrays: []*cursors.IntegerArray{
makeIntegerArray(
60,
mustParseTime("2010-01-01T00:00:00Z"), time.Minute,
func(i int64) int64 { return 2 },
),
},
wantIntegers: []*cursors.IntegerArray{
makeIntegerArray(1, maxTimestamp, 40*time.Minute, func(i int64) int64 { return 120 }),
},
},
{
name: "whole series no points",
inputArrays: []*cursors.IntegerArray{{}},
wantIntegers: []*cursors.IntegerArray{},
},
{
name: "whole series two arrays",
inputArrays: []*cursors.IntegerArray{
makeIntegerArray(
60,
mustParseTime("2010-01-01T00:00:00Z"), time.Minute,
func(i int64) int64 { return 2 },
),
makeIntegerArray(
60,
mustParseTime("2010-01-01T01:00:00Z"), time.Minute,
func(i int64) int64 { return 3 },
),
},
wantIntegers: []*cursors.IntegerArray{
makeIntegerArray(1, maxTimestamp, 40*time.Minute,
func(int64) int64 {
return 300
}),
},
},
{
name: "whole series span epoch",
inputArrays: []*cursors.IntegerArray{
makeIntegerArray(
120,
mustParseTime("1969-12-31T23:00:00Z"), time.Minute,
func(i int64) int64 { return 2 },
),
},
wantIntegers: []*cursors.IntegerArray{
makeIntegerArray(1, maxTimestamp, 40*time.Minute, func(int64) int64 { return 240 }),
},
},
{
name: "whole series span epoch two arrays",
inputArrays: []*cursors.IntegerArray{
makeIntegerArray(
60,
mustParseTime("1969-12-31T23:00:00Z"), time.Minute,
func(i int64) int64 { return 2 },
),
makeIntegerArray(
60,
mustParseTime("1970-01-01T00:00:00Z"), time.Minute,
func(i int64) int64 { return 3 },
),
},
wantIntegers: []*cursors.IntegerArray{
makeIntegerArray(1, maxTimestamp, 40*time.Minute, func(int64) int64 { return 300 }),
},
},
{
name: "whole series, with max int64 timestamp",
inputArrays: []*cursors.IntegerArray{
{
Timestamps: []int64{math.MaxInt64},
Values: []int64{100},
},
},
wantIntegers: []*cursors.IntegerArray{
{
Timestamps: []int64{math.MaxInt64},
Values: []int64{100},
},
},
},
}
for _, tc := range testcases {
tc.createCursorFn = func(cur cursors.IntegerArrayCursor, every, offset int64, window interval.Window) cursors.Cursor {
if every != 0 || offset != 0 {
window, _ = interval.NewWindow(
values.MakeDuration(every, 0, false),
values.MakeDuration(every, 0, false),
values.MakeDuration(offset, 0, false),
)
}
return newIntegerWindowSumArrayCursor(cur, window)
}
tc.run(t)
}
}
func TestWindowMinArrayCursor(t *testing.T) {
testcases := []aggArrayCursorTest{
{
name: "no window",
every: 0,
inputArrays: []*cursors.IntegerArray{
makeIntegerArray(
60,
mustParseTime("2010-01-01T00:00:00Z"), time.Minute,
func(i int64) int64 { return 100 + i },
),
},
wantIntegers: []*cursors.IntegerArray{
makeIntegerArray(1, mustParseTime("2010-01-01T00:00:00Z"), 0, func(int64) int64 { return 100 }),
},
},
{
name: "no window min int",
every: 0,
inputArrays: []*cursors.IntegerArray{
makeIntegerArray(
60,
mustParseTime("2010-01-01T00:00:00Z"), time.Minute,
func(i int64) int64 {
if i%2 == 0 {
return math.MinInt64
}
return 0
},
),
},
wantIntegers: []*cursors.IntegerArray{
makeIntegerArray(1, mustParseTime("2010-01-01T00:00:00Z"), 0, func(int64) int64 { return math.MinInt64 }),
},
},
{
name: "no window max int",
every: 0,
inputArrays: []*cursors.IntegerArray{
makeIntegerArray(
60,
mustParseTime("2010-01-01T00:00:00Z"), time.Minute,
func(i int64) int64 {
if i%2 == 0 {
return math.MaxInt64
}
return 0
},
),
},
wantIntegers: []*cursors.IntegerArray{
makeIntegerArray(1, mustParseTime("2010-01-01T00:01:00Z"), 0, func(int64) int64 { return 0 }),
},
},
{
name: "window",
every: time.Hour,
inputArrays: []*cursors.IntegerArray{
makeIntegerArray(
16,
mustParseTime("2010-01-01T00:00:00Z"), 15*time.Minute,
func(i int64) int64 {
base := (i / 4) * 100
m := (i % 4) * 15
return base + m
},
),
},
wantIntegers: []*cursors.IntegerArray{
makeIntegerArray(4, mustParseTime("2010-01-01T00:00:00Z"), time.Hour,
func(i int64) int64 { return i * 100 }),
},
},
{
name: "window offset",
every: time.Hour,
offset: 30 * time.Minute,
inputArrays: []*cursors.IntegerArray{
makeIntegerArray(
16,
mustParseTime("2010-01-01T00:00:00Z"), 15*time.Minute,
func(i int64) int64 {
base := (i / 4) * 100
m := (i % 4) * 15
return base + m
},
),
},
wantIntegers: []*cursors.IntegerArray{
{
Timestamps: []int64{
mustParseTime("2010-01-01T00:00:00Z").UnixNano(),
mustParseTime("2010-01-01T00:30:00Z").UnixNano(),
mustParseTime("2010-01-01T01:30:00Z").UnixNano(),
mustParseTime("2010-01-01T02:30:00Z").UnixNano(),
mustParseTime("2010-01-01T03:30:00Z").UnixNano(),
},
Values: []int64{0, 30, 130, 230, 330},
},
},
},
{
name: "window desc values",
every: time.Hour,
inputArrays: []*cursors.IntegerArray{
makeIntegerArray(
16,
mustParseTime("2010-01-01T00:00:00Z"), 15*time.Minute,
func(i int64) int64 {
base := (i / 4) * 100
m := 60 - (i%4)*15
return base + m
},
),
},
wantIntegers: []*cursors.IntegerArray{
makeIntegerArray(4, mustParseTime("2010-01-01T00:45:00Z"), time.Hour,
func(i int64) int64 { return i*100 + 15 }),
},
},
{
name: "window offset desc values",
every: time.Hour,
offset: 30 * time.Minute,
inputArrays: []*cursors.IntegerArray{
makeIntegerArray(
16,
mustParseTime("2010-01-01T00:00:00Z"), 15*time.Minute,
func(i int64) int64 {
base := (i / 4) * 100
m := 60 - (i%4)*15
return base + m
},
),
},
wantIntegers: []*cursors.IntegerArray{
{
Timestamps: []int64{
mustParseTime("2010-01-01T00:15:00Z").UnixNano(),
mustParseTime("2010-01-01T00:45:00Z").UnixNano(),
mustParseTime("2010-01-01T01:45:00Z").UnixNano(),
mustParseTime("2010-01-01T02:45:00Z").UnixNano(),
mustParseTime("2010-01-01T03:45:00Z").UnixNano(),
},
Values: []int64{45, 15, 115, 215, 315},
},
},
},
{
name: "window min int",
every: time.Hour,
inputArrays: []*cursors.IntegerArray{
makeIntegerArray(
16,
mustParseTime("2010-01-01T00:00:00Z"), 15*time.Minute,
func(i int64) int64 {
return math.MinInt64
},
),
},
wantIntegers: []*cursors.IntegerArray{
makeIntegerArray(4, mustParseTime("2010-01-01T00:00:00Z"), time.Hour,
func(i int64) int64 { return math.MinInt64 }),
},
},
{
name: "window max int",
every: time.Hour,
inputArrays: []*cursors.IntegerArray{
makeIntegerArray(
16,
mustParseTime("2010-01-01T00:00:00Z"), 15*time.Minute,
func(i int64) int64 {
return math.MaxInt64
},
),
},
wantIntegers: []*cursors.IntegerArray{
makeIntegerArray(4, mustParseTime("2010-01-01T00:00:00Z"), time.Hour,
func(i int64) int64 { return math.MaxInt64 }),
},
},
{
name: "empty window",
every: 15 * time.Minute,
inputArrays: []*cursors.IntegerArray{
makeIntegerArray(
2,
mustParseTime("2010-01-01T00:05:00Z"), 30*time.Minute,
func(i int64) int64 {
return 100 + i
},
),
},
wantIntegers: []*cursors.IntegerArray{
makeIntegerArray(2, mustParseTime("2010-01-01T00:05:00Z"), 30*time.Minute,
func(i int64) int64 { return 100 + i }),
},
},
{
name: "monthly windows",
window: func() interval.Window {
window, _ := interval.NewWindow(
values.MakeDuration(0, 1, false),
values.MakeDuration(0, 1, false),
values.MakeDuration(0, 0, false),
)
return window
}(),
inputArrays: []*cursors.IntegerArray{
makeIntegerArray(
1,
mustParseTime("2010-01-01T00:00:00Z"), time.Minute,
func(i int64) int64 { return 100 + i },
),
},
wantIntegers: []*cursors.IntegerArray{
makeIntegerArray(1, mustParseTime("2010-01-01T00:00:00Z"), 0, func(int64) int64 { return 100 }),
},
},
}
for _, tc := range testcases {
tc.createCursorFn = func(cur cursors.IntegerArrayCursor, every, offset int64, window interval.Window) cursors.Cursor {
if every != 0 || offset != 0 {
window, _ = interval.NewWindow(
values.MakeDuration(every, 0, false),
values.MakeDuration(every, 0, false),
values.MakeDuration(offset, 0, false),
)
}
return newIntegerWindowMinArrayCursor(cur, window)
}
tc.run(t)
}
}
func TestWindowMaxArrayCursor(t *testing.T) {
testcases := []aggArrayCursorTest{
{
name: "no window",
every: 0,
inputArrays: []*cursors.IntegerArray{
makeIntegerArray(
60,
mustParseTime("2010-01-01T00:00:00Z"), time.Minute,
func(i int64) int64 { return 100 + i },
),
},
wantIntegers: []*cursors.IntegerArray{
makeIntegerArray(1, mustParseTime("2010-01-01T00:59:00Z"), 0, func(int64) int64 { return 159 }),
},
},
{
name: "no window min int",
every: 0,
inputArrays: []*cursors.IntegerArray{
makeIntegerArray(
60,
mustParseTime("2010-01-01T00:00:00Z"), time.Minute,
func(i int64) int64 {
if i%2 == 0 {
return math.MinInt64
}
return 0
},
),
},
wantIntegers: []*cursors.IntegerArray{
makeIntegerArray(1, mustParseTime("2010-01-01T00:01:00Z"), 0, func(int64) int64 { return 0 }),
},
},
{
name: "no window max int",
every: 0,
inputArrays: []*cursors.IntegerArray{
makeIntegerArray(
60,
mustParseTime("2010-01-01T00:00:00Z"), time.Minute,
func(i int64) int64 {
if i%2 == 0 {
return math.MaxInt64
}
return 0
},
),
},
wantIntegers: []*cursors.IntegerArray{
makeIntegerArray(1, mustParseTime("2010-01-01T00:00:00Z"), 0, func(int64) int64 { return math.MaxInt64 }),
},
},
{
name: "window",
every: time.Hour,
inputArrays: []*cursors.IntegerArray{
makeIntegerArray(
16,
mustParseTime("2010-01-01T00:00:00Z"), 15*time.Minute,
func(i int64) int64 {
base := (i / 4) * 100
m := (i % 4) * 15
return base + m
},
),
},
wantIntegers: []*cursors.IntegerArray{
makeIntegerArray(4, mustParseTime("2010-01-01T00:45:00Z"), time.Hour,
func(i int64) int64 { return i*100 + 45 }),
},
},
{
name: "window offset",
every: time.Hour,
offset: 30 * time.Minute,
inputArrays: []*cursors.IntegerArray{
makeIntegerArray(
16,
mustParseTime("2010-01-01T00:00:00Z"), 15*time.Minute,
func(i int64) int64 {
base := (i / 4) * 100
m := (i % 4) * 15
return base + m
},
),
},
wantIntegers: []*cursors.IntegerArray{
{
Timestamps: []int64{
mustParseTime("2010-01-01T00:15:00Z").UnixNano(),
mustParseTime("2010-01-01T01:15:00Z").UnixNano(),
mustParseTime("2010-01-01T02:15:00Z").UnixNano(),
mustParseTime("2010-01-01T03:15:00Z").UnixNano(),
mustParseTime("2010-01-01T03:45:00Z").UnixNano(),
},
Values: []int64{15, 115, 215, 315, 345},
},
},
},
{
name: "window desc values",
every: time.Hour,
inputArrays: []*cursors.IntegerArray{
makeIntegerArray(
16,
mustParseTime("2010-01-01T00:00:00Z"), 15*time.Minute,
func(i int64) int64 {
base := (i / 4) * 100
m := 60 - (i%4)*15
return base + m
},
),
},
wantIntegers: []*cursors.IntegerArray{
makeIntegerArray(4, mustParseTime("2010-01-01T00:00:00Z"), time.Hour,
func(i int64) int64 { return i*100 + 60 }),
},
},
{
name: "window offset desc values",
every: time.Hour,
offset: 30 * time.Minute,
inputArrays: []*cursors.IntegerArray{
makeIntegerArray(
16,
mustParseTime("2010-01-01T00:00:00Z"), 15*time.Minute,
func(i int64) int64 {
base := (i / 4) * 100
m := 60 - (i%4)*15
return base + m
},
),
},
wantIntegers: []*cursors.IntegerArray{
{
Timestamps: []int64{
mustParseTime("2010-01-01T00:00:00Z").UnixNano(),
mustParseTime("2010-01-01T01:00:00Z").UnixNano(),
mustParseTime("2010-01-01T02:00:00Z").UnixNano(),
mustParseTime("2010-01-01T03:00:00Z").UnixNano(),
mustParseTime("2010-01-01T03:30:00Z").UnixNano(),
},
Values: []int64{60, 160, 260, 360, 330},
},
},
},
{
name: "window min int",
every: time.Hour,
inputArrays: []*cursors.IntegerArray{
makeIntegerArray(
16,
mustParseTime("2010-01-01T00:00:00Z"), 15*time.Minute,
func(i int64) int64 {
return math.MinInt64
},
),
},
wantIntegers: []*cursors.IntegerArray{
makeIntegerArray(4, mustParseTime("2010-01-01T00:00:00Z"), time.Hour,
func(i int64) int64 { return math.MinInt64 }),
},
},
{
name: "window max int",
every: time.Hour,
inputArrays: []*cursors.IntegerArray{
makeIntegerArray(
16,
mustParseTime("2010-01-01T00:00:00Z"), 15*time.Minute,
func(i int64) int64 {
return math.MaxInt64
},
),
},
wantIntegers: []*cursors.IntegerArray{
makeIntegerArray(4, mustParseTime("2010-01-01T00:00:00Z"), time.Hour,
func(i int64) int64 { return math.MaxInt64 }),
},
},
{
name: "empty window",
every: 15 * time.Minute,
inputArrays: []*cursors.IntegerArray{
makeIntegerArray(
2,
mustParseTime("2010-01-01T00:05:00Z"), 30*time.Minute,
func(i int64) int64 {
return 100 + i
},
),
},
wantIntegers: []*cursors.IntegerArray{
makeIntegerArray(2, mustParseTime("2010-01-01T00:05:00Z"), 30*time.Minute,
func(i int64) int64 { return 100 + i }),
},
},
}
for _, tc := range testcases {
tc.createCursorFn = func(cur cursors.IntegerArrayCursor, every, offset int64, window interval.Window) cursors.Cursor {
if every != 0 || offset != 0 {
window, _ = interval.NewWindow(
values.MakeDuration(every, 0, false),
values.MakeDuration(every, 0, false),
values.MakeDuration(offset, 0, false),
)
}
return newIntegerWindowMaxArrayCursor(cur, window)
}
tc.run(t)
}
}
func TestWindowMeanArrayCursor(t *testing.T) {
maxTimestamp := time.Unix(0, math.MaxInt64)
testcases := []aggArrayCursorTest{
{
name: "no window",
every: 0,
inputArrays: []*cursors.IntegerArray{
makeIntegerArray(
5,
mustParseTime("2010-01-01T00:00:00Z"), time.Minute,
func(i int64) int64 { return i + 1 },
),
},
wantFloats: []*cursors.FloatArray{
makeFloatArray(1, maxTimestamp, 0, func(int64) float64 { return 3.0 }),
},
},
{
name: "no window fraction result",
every: 0,
inputArrays: []*cursors.IntegerArray{
makeIntegerArray(
6,
mustParseTime("2010-01-01T00:00:00Z"), time.Minute,
func(i int64) int64 { return i + 1 },
),
},
wantFloats: []*cursors.FloatArray{
makeFloatArray(1, maxTimestamp, 0, func(int64) float64 { return 3.5 }),
},
},
{
name: "no window empty",
every: 0,
inputArrays: []*cursors.IntegerArray{},
wantFloats: []*cursors.FloatArray{},
},
{
name: "window",
every: 30 * time.Minute,
inputArrays: []*cursors.IntegerArray{
makeIntegerArray(
8,
mustParseTime("2010-01-01T00:00:00Z"), 15*time.Minute,
func(i int64) int64 {
return i
},
),
},
wantFloats: []*cursors.FloatArray{
makeFloatArray(4, mustParseTime("2010-01-01T00:30:00Z"), 30*time.Minute,
func(i int64) float64 { return 0.5 + float64(i)*2 }),
},
},
{
name: "window offset",
every: 30 * time.Minute,
offset: 5 * time.Minute,
inputArrays: []*cursors.IntegerArray{
makeIntegerArray(
8,
mustParseTime("2010-01-01T00:00:00Z"), 15*time.Minute,
func(i int64) int64 {
return i
},
),
},
wantFloats: []*cursors.FloatArray{
makeFloatArray(5, mustParseTime("2010-01-01T00:05:00Z"), 30*time.Minute,
func(i int64) float64 { return []float64{0, 1.5, 3.5, 5.5, 7}[i] }),
},
},
{
name: "empty window",
every: 15 * time.Minute,
inputArrays: []*cursors.IntegerArray{
makeIntegerArray(
2,
mustParseTime("2010-01-01T00:05:00Z"), 30*time.Minute,
func(i int64) int64 {
return 100 + i
},
),
},
wantFloats: []*cursors.FloatArray{
makeFloatArray(2, mustParseTime("2010-01-01T00:15:00Z"), 30*time.Minute,
func(i int64) float64 { return 100 + float64(i) }),
},
},
}
for _, tc := range testcases {
tc.createCursorFn = func(cur cursors.IntegerArrayCursor, every, offset int64, window interval.Window) cursors.Cursor {
if every != 0 || offset != 0 {
window, _ = interval.NewWindow(
values.MakeDuration(every, 0, false),
values.MakeDuration(every, 0, false),
values.MakeDuration(offset, 0, false),
)
}
return newIntegerWindowMeanArrayCursor(cur, window)
}
tc.run(t)
}
}
// This test replicates GitHub issue
// https://github.com/influxdata/influxdb/issues/20035
func TestMultiShardArrayCursor(t *testing.T) {
t.Run("should drain all CursorIterators", func(t *testing.T) {
ctrl := gomock.NewController(t)
t.Cleanup(ctrl.Finish)
var (
emptyArray = cursors.NewIntegerArrayLen(0)
oneElementArray = cursors.NewIntegerArrayLen(1)
iter cursors.CursorIterators
)
{
mc := mock.NewMockIntegerArrayCursor(ctrl)
mc.EXPECT().
Next().
Return(oneElementArray)
mc.EXPECT().
Next().
Return(emptyArray)
mc.EXPECT().
Close()
ci := mock.NewMockCursorIterator(ctrl)
ci.EXPECT().
Next(gomock.Any(), gomock.Any()).
Return(mc, nil)
iter = append(iter, ci)
}
// return an empty cursor, which should be skipped
{
ci := mock.NewMockCursorIterator(ctrl)
ci.EXPECT().
Next(gomock.Any(), gomock.Any()).
Return(nil, nil)
iter = append(iter, ci)
}
{
mc := mock.NewMockIntegerArrayCursor(ctrl)
mc.EXPECT().
Next().
Return(oneElementArray)
mc.EXPECT().
Next().
Return(emptyArray)
ci := mock.NewMockCursorIterator(ctrl)
ci.EXPECT().
Next(gomock.Any(), gomock.Any()).
Return(mc, nil)
iter = append(iter, ci)
}
row := SeriesRow{Query: iter}
ctx := context.Background()
msac := newMultiShardArrayCursors(ctx, models.MinNanoTime, models.MaxNanoTime, true)
cur, ok := msac.createCursor(row).(cursors.IntegerArrayCursor)
require.Truef(t, ok, "Expected IntegerArrayCursor")
ia := cur.Next()
require.NotNil(t, ia)
require.Equal(t, 1, ia.Len())
ia = cur.Next()
require.NotNil(t, ia)
require.Equal(t, 1, ia.Len())
ia = cur.Next()
require.NotNil(t, ia)
require.Equal(t, 0, ia.Len())
})
}
type MockExpression struct {
EvalBoolFunc func(v Valuer) bool
}
func (e *MockExpression) EvalBool(v Valuer) bool { return e.EvalBoolFunc(v) }