github.com/influxdata/influxdb/v2@v2.7.6/influxql/query/subquery_test.go (about) 1 package query_test 2 3 import ( 4 "context" 5 "testing" 6 "time" 7 8 "github.com/google/go-cmp/cmp" 9 "github.com/influxdata/influxdb/v2/influxql/query" 10 "github.com/influxdata/influxql" 11 ) 12 13 type CreateIteratorFn func(ctx context.Context, m *influxql.Measurement, opt query.IteratorOptions) query.Iterator 14 15 func TestSubquery(t *testing.T) { 16 for _, test := range []struct { 17 Name string 18 Statement string 19 Fields map[string]influxql.DataType 20 MapShardsFn func(t *testing.T, tr influxql.TimeRange) CreateIteratorFn 21 Rows []query.Row 22 }{ 23 { 24 Name: "AuxiliaryFields", 25 Statement: `SELECT max / 2.0 FROM (SELECT max(value) FROM cpu GROUP BY time(5s)) WHERE time >= '1970-01-01T00:00:00Z' AND time < '1970-01-01T00:00:15Z'`, 26 Fields: map[string]influxql.DataType{"value": influxql.Float}, 27 MapShardsFn: func(t *testing.T, tr influxql.TimeRange) CreateIteratorFn { 28 if got, want := tr.MinTimeNano(), 0*Second; got != want { 29 t.Errorf("unexpected min time: got=%d want=%d", got, want) 30 } 31 if got, want := tr.MaxTimeNano(), 15*Second-1; got != want { 32 t.Errorf("unexpected max time: got=%d want=%d", got, want) 33 } 34 return func(ctx context.Context, m *influxql.Measurement, opt query.IteratorOptions) query.Iterator { 35 if got, want := m.Name, "cpu"; got != want { 36 t.Errorf("unexpected source: got=%s want=%s", got, want) 37 } 38 if got, want := opt.Expr.String(), "max(value::float)"; got != want { 39 t.Errorf("unexpected expression: got=%s want=%s", got, want) 40 } 41 return &FloatIterator{Points: []query.FloatPoint{ 42 {Name: "cpu", Time: 0 * Second, Value: 5}, 43 {Name: "cpu", Time: 5 * Second, Value: 3}, 44 {Name: "cpu", Time: 10 * Second, Value: 8}, 45 }} 46 } 47 }, 48 Rows: []query.Row{ 49 {Time: 0 * Second, Series: query.Series{Name: "cpu"}, Values: []interface{}{2.5}}, 50 {Time: 5 * Second, Series: query.Series{Name: "cpu"}, Values: []interface{}{1.5}}, 51 {Time: 10 * Second, Series: query.Series{Name: "cpu"}, Values: []interface{}{float64(4)}}, 52 }, 53 }, 54 { 55 Name: "AuxiliaryFields_WithWhereClause", 56 Statement: `SELECT host FROM (SELECT max(value), host FROM cpu GROUP BY time(5s)) WHERE max > 4 AND time >= '1970-01-01T00:00:00Z' AND time < '1970-01-01T00:00:15Z'`, 57 Fields: map[string]influxql.DataType{ 58 "value": influxql.Float, 59 "host": influxql.Tag, 60 }, 61 MapShardsFn: func(t *testing.T, tr influxql.TimeRange) CreateIteratorFn { 62 if got, want := tr.MinTimeNano(), 0*Second; got != want { 63 t.Errorf("unexpected min time: got=%d want=%d", got, want) 64 } 65 if got, want := tr.MaxTimeNano(), 15*Second-1; got != want { 66 t.Errorf("unexpected max time: got=%d want=%d", got, want) 67 } 68 return func(ctx context.Context, m *influxql.Measurement, opt query.IteratorOptions) query.Iterator { 69 if got, want := m.Name, "cpu"; got != want { 70 t.Errorf("unexpected source: got=%s want=%s", got, want) 71 } 72 if got, want := opt.Expr.String(), "max(value::float)"; got != want { 73 t.Errorf("unexpected expression: got=%s want=%s", got, want) 74 } 75 if got, want := opt.Aux, []influxql.VarRef{{Val: "host", Type: influxql.Tag}}; !cmp.Equal(got, want) { 76 t.Errorf("unexpected auxiliary fields:\n%s", cmp.Diff(want, got)) 77 } 78 return &FloatIterator{Points: []query.FloatPoint{ 79 {Name: "cpu", Time: 0 * Second, Value: 5, Aux: []interface{}{"server02"}}, 80 {Name: "cpu", Time: 5 * Second, Value: 3, Aux: []interface{}{"server01"}}, 81 {Name: "cpu", Time: 10 * Second, Value: 8, Aux: []interface{}{"server03"}}, 82 }} 83 } 84 }, 85 Rows: []query.Row{ 86 {Time: 0 * Second, Series: query.Series{Name: "cpu"}, Values: []interface{}{"server02"}}, 87 {Time: 10 * Second, Series: query.Series{Name: "cpu"}, Values: []interface{}{"server03"}}, 88 }, 89 }, 90 { 91 Name: "AuxiliaryFields_NonExistentField", 92 Statement: `SELECT host FROM (SELECT max(value) FROM cpu GROUP BY time(5s)) WHERE time >= '1970-01-01T00:00:00Z' AND time < '1970-01-01T00:00:15Z'`, 93 Fields: map[string]influxql.DataType{"value": influxql.Float}, 94 MapShardsFn: func(t *testing.T, tr influxql.TimeRange) CreateIteratorFn { 95 return func(ctx context.Context, m *influxql.Measurement, opt query.IteratorOptions) query.Iterator { 96 return &FloatIterator{Points: []query.FloatPoint{ 97 {Name: "cpu", Time: 0 * Second, Value: 5}, 98 {Name: "cpu", Time: 5 * Second, Value: 3}, 99 {Name: "cpu", Time: 10 * Second, Value: 8}, 100 }} 101 } 102 }, 103 Rows: []query.Row(nil), 104 }, 105 { 106 Name: "AggregateOfMath", 107 Statement: `SELECT mean(percentage) FROM (SELECT value * 100.0 AS percentage FROM cpu) WHERE time >= '1970-01-01T00:00:00Z' AND time < '1970-01-01T00:00:15Z' GROUP BY time(5s)`, 108 Fields: map[string]influxql.DataType{"value": influxql.Float}, 109 MapShardsFn: func(t *testing.T, tr influxql.TimeRange) CreateIteratorFn { 110 if got, want := tr.MinTimeNano(), 0*Second; got != want { 111 t.Errorf("unexpected min time: got=%d want=%d", got, want) 112 } 113 if got, want := tr.MaxTimeNano(), 15*Second-1; got != want { 114 t.Errorf("unexpected max time: got=%d want=%d", got, want) 115 } 116 return func(ctx context.Context, m *influxql.Measurement, opt query.IteratorOptions) query.Iterator { 117 if got, want := m.Name, "cpu"; got != want { 118 t.Errorf("unexpected source: got=%s want=%s", got, want) 119 } 120 if got, want := opt.Expr, influxql.Expr(nil); got != want { 121 t.Errorf("unexpected expression: got=%s want=%s", got, want) 122 } 123 if got, want := opt.Aux, []influxql.VarRef{{Val: "value", Type: influxql.Float}}; !cmp.Equal(got, want) { 124 t.Errorf("unexpected auxiliary fields:\n%s", cmp.Diff(want, got)) 125 } 126 return &FloatIterator{Points: []query.FloatPoint{ 127 {Name: "cpu", Time: 0 * Second, Aux: []interface{}{0.5}}, 128 {Name: "cpu", Time: 2 * Second, Aux: []interface{}{1.0}}, 129 {Name: "cpu", Time: 5 * Second, Aux: []interface{}{0.05}}, 130 {Name: "cpu", Time: 8 * Second, Aux: []interface{}{0.45}}, 131 {Name: "cpu", Time: 12 * Second, Aux: []interface{}{0.34}}, 132 }} 133 } 134 }, 135 Rows: []query.Row{ 136 {Time: 0 * Second, Series: query.Series{Name: "cpu"}, Values: []interface{}{float64(75)}}, 137 {Time: 5 * Second, Series: query.Series{Name: "cpu"}, Values: []interface{}{float64(25)}}, 138 {Time: 10 * Second, Series: query.Series{Name: "cpu"}, Values: []interface{}{float64(34)}}, 139 }, 140 }, 141 { 142 Name: "Cast", 143 Statement: `SELECT value::integer FROM (SELECT mean(value) AS value FROM cpu)`, 144 Fields: map[string]influxql.DataType{"value": influxql.Integer}, 145 MapShardsFn: func(t *testing.T, tr influxql.TimeRange) CreateIteratorFn { 146 return func(ctx context.Context, m *influxql.Measurement, opt query.IteratorOptions) query.Iterator { 147 if got, want := m.Name, "cpu"; got != want { 148 t.Errorf("unexpected source: got=%s want=%s", got, want) 149 } 150 if got, want := opt.Expr.String(), "mean(value::integer)"; got != want { 151 t.Errorf("unexpected expression: got=%s want=%s", got, want) 152 } 153 return &FloatIterator{Points: []query.FloatPoint{ 154 {Name: "cpu", Time: 0 * Second, Value: float64(20) / float64(6)}, 155 }} 156 } 157 }, 158 Rows: []query.Row{ 159 {Time: 0 * Second, Series: query.Series{Name: "cpu"}, Values: []interface{}{int64(3)}}, 160 }, 161 }, 162 { 163 Name: "CountTag", 164 Statement: `SELECT count(host) FROM (SELECT value, host FROM cpu) WHERE time >= '1970-01-01T00:00:00Z' AND time < '1970-01-01T00:00:15Z'`, 165 Fields: map[string]influxql.DataType{ 166 "value": influxql.Float, 167 "host": influxql.Tag, 168 }, 169 MapShardsFn: func(t *testing.T, tr influxql.TimeRange) CreateIteratorFn { 170 if got, want := tr.MinTimeNano(), 0*Second; got != want { 171 t.Errorf("unexpected min time: got=%d want=%d", got, want) 172 } 173 if got, want := tr.MaxTimeNano(), 15*Second-1; got != want { 174 t.Errorf("unexpected max time: got=%d want=%d", got, want) 175 } 176 return func(ctx context.Context, m *influxql.Measurement, opt query.IteratorOptions) query.Iterator { 177 if got, want := m.Name, "cpu"; got != want { 178 t.Errorf("unexpected source: got=%s want=%s", got, want) 179 } 180 if got, want := opt.Aux, []influxql.VarRef{ 181 {Val: "host", Type: influxql.Tag}, 182 {Val: "value", Type: influxql.Float}, 183 }; !cmp.Equal(got, want) { 184 t.Errorf("unexpected auxiliary fields:\n%s", cmp.Diff(want, got)) 185 } 186 return &FloatIterator{Points: []query.FloatPoint{ 187 {Name: "cpu", Aux: []interface{}{"server01", 5.0}}, 188 {Name: "cpu", Aux: []interface{}{"server02", 3.0}}, 189 {Name: "cpu", Aux: []interface{}{"server03", 8.0}}, 190 }} 191 } 192 }, 193 Rows: []query.Row{ 194 {Time: 0 * Second, Series: query.Series{Name: "cpu"}, Values: []interface{}{int64(3)}}, 195 }, 196 }, 197 { 198 Name: "StripTags", 199 Statement: `SELECT max FROM (SELECT max(value) FROM cpu GROUP BY host) WHERE time >= '1970-01-01T00:00:00Z' AND time < '1970-01-01T00:00:15Z'`, 200 Fields: map[string]influxql.DataType{"value": influxql.Float}, 201 MapShardsFn: func(t *testing.T, tr influxql.TimeRange) CreateIteratorFn { 202 if got, want := tr.MinTimeNano(), 0*Second; got != want { 203 t.Errorf("unexpected min time: got=%d want=%d", got, want) 204 } 205 if got, want := tr.MaxTimeNano(), 15*Second-1; got != want { 206 t.Errorf("unexpected max time: got=%d want=%d", got, want) 207 } 208 return func(ctx context.Context, m *influxql.Measurement, opt query.IteratorOptions) query.Iterator { 209 if got, want := m.Name, "cpu"; got != want { 210 t.Errorf("unexpected source: got=%s want=%s", got, want) 211 } 212 if got, want := opt.Expr.String(), "max(value::float)"; got != want { 213 t.Errorf("unexpected expression: got=%s want=%s", got, want) 214 } 215 return &FloatIterator{Points: []query.FloatPoint{ 216 {Name: "cpu", Tags: ParseTags("host=server01"), Value: 5}, 217 {Name: "cpu", Tags: ParseTags("host=server02"), Value: 3}, 218 {Name: "cpu", Tags: ParseTags("host=server03"), Value: 8}, 219 }} 220 } 221 }, 222 Rows: []query.Row{ 223 {Time: 0 * Second, Series: query.Series{Name: "cpu"}, Values: []interface{}{5.0}}, 224 {Time: 0 * Second, Series: query.Series{Name: "cpu"}, Values: []interface{}{3.0}}, 225 {Time: 0 * Second, Series: query.Series{Name: "cpu"}, Values: []interface{}{8.0}}, 226 }, 227 }, 228 { 229 Name: "DifferentDimensionsWithSelectors", 230 Statement: `SELECT sum("max_min") FROM ( 231 SELECT max("value") - min("value") FROM cpu GROUP BY time(30s), host 232 ) WHERE time >= '1970-01-01T00:00:00Z' AND time < '1970-01-01T00:01:00Z' GROUP BY time(30s)`, 233 Fields: map[string]influxql.DataType{"value": influxql.Float}, 234 MapShardsFn: func(t *testing.T, tr influxql.TimeRange) CreateIteratorFn { 235 if got, want := tr.MinTimeNano(), 0*Second; got != want { 236 t.Errorf("unexpected min time: got=%d want=%d", got, want) 237 } 238 if got, want := tr.MaxTimeNano(), 60*Second-1; got != want { 239 t.Errorf("unexpected max time: got=%d want=%d", got, want) 240 } 241 return func(ctx context.Context, m *influxql.Measurement, opt query.IteratorOptions) query.Iterator { 242 if got, want := m.Name, "cpu"; got != want { 243 t.Errorf("unexpected source: got=%s want=%s", got, want) 244 } 245 246 var itr query.Iterator = &FloatIterator{Points: []query.FloatPoint{ 247 {Name: "cpu", Tags: ParseTags("host=A"), Time: 0 * Second, Value: 2}, 248 {Name: "cpu", Tags: ParseTags("host=A"), Time: 10 * Second, Value: 7}, 249 {Name: "cpu", Tags: ParseTags("host=A"), Time: 20 * Second, Value: 3}, 250 {Name: "cpu", Tags: ParseTags("host=B"), Time: 0 * Second, Value: 8}, 251 {Name: "cpu", Tags: ParseTags("host=B"), Time: 10 * Second, Value: 3}, 252 {Name: "cpu", Tags: ParseTags("host=B"), Time: 20 * Second, Value: 7}, 253 {Name: "cpu", Tags: ParseTags("host=A"), Time: 30 * Second, Value: 2}, 254 {Name: "cpu", Tags: ParseTags("host=A"), Time: 40 * Second, Value: 1}, 255 {Name: "cpu", Tags: ParseTags("host=A"), Time: 50 * Second, Value: 9}, 256 {Name: "cpu", Tags: ParseTags("host=B"), Time: 30 * Second, Value: 2}, 257 {Name: "cpu", Tags: ParseTags("host=B"), Time: 40 * Second, Value: 2}, 258 {Name: "cpu", Tags: ParseTags("host=B"), Time: 50 * Second, Value: 2}, 259 }} 260 if _, ok := opt.Expr.(*influxql.Call); ok { 261 i, err := query.NewCallIterator(itr, opt) 262 if err != nil { 263 panic(err) 264 } 265 itr = i 266 } 267 return itr 268 } 269 }, 270 Rows: []query.Row{ 271 {Time: 0 * Second, Series: query.Series{Name: "cpu"}, Values: []interface{}{float64(10)}}, 272 {Time: 30 * Second, Series: query.Series{Name: "cpu"}, Values: []interface{}{float64(8)}}, 273 }, 274 }, 275 { 276 Name: "TimeOrderingInTheOuterQuery", 277 Statement: `select * from (select last(value) from cpu group by host) order by time asc`, 278 Fields: map[string]influxql.DataType{"value": influxql.Float}, 279 MapShardsFn: func(t *testing.T, tr influxql.TimeRange) CreateIteratorFn { 280 281 return func(ctx context.Context, m *influxql.Measurement, opt query.IteratorOptions) query.Iterator { 282 if got, want := m.Name, "cpu"; got != want { 283 t.Errorf("unexpected source: got=%s want=%s", got, want) 284 } 285 286 var itr query.Iterator = &FloatIterator{Points: []query.FloatPoint{ 287 {Name: "cpu", Tags: ParseTags("host=A"), Time: 0 * Second, Value: 2}, 288 {Name: "cpu", Tags: ParseTags("host=A"), Time: 10 * Second, Value: 7}, 289 {Name: "cpu", Tags: ParseTags("host=A"), Time: 20 * Second, Value: 3}, 290 {Name: "cpu", Tags: ParseTags("host=B"), Time: 0 * Second, Value: 8}, 291 {Name: "cpu", Tags: ParseTags("host=B"), Time: 10 * Second, Value: 3}, 292 {Name: "cpu", Tags: ParseTags("host=B"), Time: 19 * Second, Value: 7}, 293 }} 294 if _, ok := opt.Expr.(*influxql.Call); ok { 295 i, err := query.NewCallIterator(itr, opt) 296 if err != nil { 297 panic(err) 298 } 299 itr = i 300 } 301 return itr 302 } 303 }, 304 Rows: []query.Row{ 305 {Time: 19 * Second, Series: query.Series{Name: "cpu"}, Values: []interface{}{"B", float64(7)}}, 306 {Time: 20 * Second, Series: query.Series{Name: "cpu"}, Values: []interface{}{"A", float64(3)}}, 307 }, 308 }, 309 { 310 Name: "TimeZone", 311 Statement: `SELECT * FROM (SELECT * FROM cpu WHERE time >= '2019-04-17 09:00:00' and time < '2019-04-17 10:00:00' TZ('America/Chicago'))`, 312 Fields: map[string]influxql.DataType{"value": influxql.Float}, 313 MapShardsFn: func(t *testing.T, tr influxql.TimeRange) CreateIteratorFn { 314 return func(ctx context.Context, m *influxql.Measurement, opt query.IteratorOptions) query.Iterator { 315 if got, want := time.Unix(0, opt.StartTime).UTC(), mustParseTime("2019-04-17T14:00:00Z"); !got.Equal(want) { 316 t.Errorf("unexpected min time: got=%q want=%q", got, want) 317 } 318 if got, want := time.Unix(0, opt.EndTime).UTC(), mustParseTime("2019-04-17T15:00:00Z").Add(-1); !got.Equal(want) { 319 t.Errorf("unexpected max time: got=%q want=%q", got, want) 320 } 321 return &FloatIterator{} 322 } 323 }, 324 }, 325 { 326 Name: "DifferentDimensionsOrderByDesc", 327 Statement: `SELECT value, mytag FROM (SELECT last(value) AS value FROM testing GROUP BY mytag) ORDER BY desc`, 328 Fields: map[string]influxql.DataType{"value": influxql.Float}, 329 MapShardsFn: func(t *testing.T, tr influxql.TimeRange) CreateIteratorFn { 330 return func(ctx context.Context, m *influxql.Measurement, opt query.IteratorOptions) query.Iterator { 331 if got, want := m.Name, "testing"; got != want { 332 t.Errorf("unexpected source: got=%s want=%s", got, want) 333 } 334 335 if opt.Ascending { 336 t.Error("expected iterator to be descending, not ascending") 337 } 338 339 var itr query.Iterator = &FloatIterator{Points: []query.FloatPoint{ 340 {Name: "testing", Tags: ParseTags("mytag=c"), Time: mustParseTime("2019-06-25T22:36:20.93605779Z").UnixNano(), Value: 2}, 341 {Name: "testing", Tags: ParseTags("mytag=c"), Time: mustParseTime("2019-06-25T22:36:20.671604877Z").UnixNano(), Value: 2}, 342 {Name: "testing", Tags: ParseTags("mytag=c"), Time: mustParseTime("2019-06-25T22:36:20.255794481Z").UnixNano(), Value: 2}, 343 {Name: "testing", Tags: ParseTags("mytag=b"), Time: mustParseTime("2019-06-25T22:36:18.176662543Z").UnixNano(), Value: 2}, 344 {Name: "testing", Tags: ParseTags("mytag=b"), Time: mustParseTime("2019-06-25T22:36:17.815979113Z").UnixNano(), Value: 2}, 345 {Name: "testing", Tags: ParseTags("mytag=b"), Time: mustParseTime("2019-06-25T22:36:17.265031598Z").UnixNano(), Value: 2}, 346 {Name: "testing", Tags: ParseTags("mytag=a"), Time: mustParseTime("2019-06-25T22:36:15.144253616Z").UnixNano(), Value: 2}, 347 {Name: "testing", Tags: ParseTags("mytag=a"), Time: mustParseTime("2019-06-25T22:36:14.719167205Z").UnixNano(), Value: 2}, 348 {Name: "testing", Tags: ParseTags("mytag=a"), Time: mustParseTime("2019-06-25T22:36:13.711721316Z").UnixNano(), Value: 2}, 349 }} 350 if _, ok := opt.Expr.(*influxql.Call); ok { 351 i, err := query.NewCallIterator(itr, opt) 352 if err != nil { 353 panic(err) 354 } 355 itr = i 356 } 357 return itr 358 } 359 }, 360 Rows: []query.Row{ 361 {Time: mustParseTime("2019-06-25T22:36:20.93605779Z").UnixNano(), Series: query.Series{Name: "testing"}, Values: []interface{}{float64(2), "c"}}, 362 {Time: mustParseTime("2019-06-25T22:36:18.176662543Z").UnixNano(), Series: query.Series{Name: "testing"}, Values: []interface{}{float64(2), "b"}}, 363 {Time: mustParseTime("2019-06-25T22:36:15.144253616Z").UnixNano(), Series: query.Series{Name: "testing"}, Values: []interface{}{float64(2), "a"}}, 364 }, 365 }, 366 } { 367 t.Run(test.Name, func(t *testing.T) { 368 shardMapper := ShardMapper{ 369 MapShardsFn: func(_ context.Context, sources influxql.Sources, tr influxql.TimeRange) query.ShardGroup { 370 fn := test.MapShardsFn(t, tr) 371 return &ShardGroup{ 372 Fields: test.Fields, 373 CreateIteratorFn: func(ctx context.Context, m *influxql.Measurement, opt query.IteratorOptions) (query.Iterator, error) { 374 return fn(ctx, m, opt), nil 375 }, 376 } 377 }, 378 } 379 380 stmt := MustParseSelectStatement(test.Statement) 381 stmt.OmitTime = true 382 cur, err := query.Select(context.Background(), stmt, &shardMapper, query.SelectOptions{}) 383 if err != nil { 384 t.Fatalf("unexpected parse error: %s", err) 385 } else if a, err := ReadCursor(cur); err != nil { 386 t.Fatalf("unexpected error: %s", err) 387 } else if diff := cmp.Diff(test.Rows, a); diff != "" { 388 t.Fatalf("unexpected points:\n%s", diff) 389 } 390 }) 391 } 392 } 393 394 // Ensure that the subquery gets passed the max series limit. 395 func TestSubquery_MaxSeriesN(t *testing.T) { 396 shardMapper := ShardMapper{ 397 MapShardsFn: func(_ context.Context, sources influxql.Sources, tr influxql.TimeRange) query.ShardGroup { 398 return &ShardGroup{ 399 Fields: map[string]influxql.DataType{ 400 "value": influxql.Float, 401 }, 402 CreateIteratorFn: func(ctx context.Context, m *influxql.Measurement, opt query.IteratorOptions) (query.Iterator, error) { 403 if opt.MaxSeriesN != 1000 { 404 t.Errorf("max series limit has not been set") 405 } 406 return nil, nil 407 }, 408 } 409 }, 410 } 411 412 stmt := MustParseSelectStatement(`SELECT max(value) FROM (SELECT value FROM cpu)`) 413 cur, err := query.Select(context.Background(), stmt, &shardMapper, query.SelectOptions{ 414 MaxSeriesN: 1000, 415 }) 416 if err != nil { 417 t.Fatalf("unexpected error: %s", err) 418 } 419 cur.Close() 420 }