2014-12-06 18:17:58 +00:00
package influxql_test
import (
2015-01-22 04:40:03 +00:00
"fmt"
2015-01-28 00:20:34 +00:00
"reflect"
2014-12-06 18:17:58 +00:00
"strings"
"testing"
2015-01-20 02:44:47 +00:00
"time"
2014-12-06 18:17:58 +00:00
2016-02-10 17:26:18 +00:00
"github.com/influxdata/influxdb/influxql"
2014-12-06 18:17:58 +00:00
)
2016-05-24 11:25:01 +00:00
func BenchmarkQuery_String ( b * testing . B ) {
p := influxql . NewParser ( strings . NewReader ( ` SELECT foo AS zoo, a AS b FROM bar WHERE value > 10 AND q = 'hello' ` ) )
q , _ := p . ParseStatement ( )
for i := 0 ; i < b . N ; i ++ {
_ = q . String ( )
}
}
2014-12-09 15:45:29 +00:00
// Ensure a value's data type can be retrieved.
func TestInspectDataType ( t * testing . T ) {
for i , tt := range [ ] struct {
v interface { }
typ influxql . DataType
} {
2015-04-13 04:36:00 +00:00
{ float64 ( 100 ) , influxql . Float } ,
2016-01-23 04:22:50 +00:00
{ int64 ( 100 ) , influxql . Integer } ,
{ int32 ( 100 ) , influxql . Integer } ,
{ 100 , influxql . Integer } ,
{ true , influxql . Boolean } ,
{ "string" , influxql . String } ,
{ time . Now ( ) , influxql . Time } ,
{ time . Second , influxql . Duration } ,
{ nil , influxql . Unknown } ,
2014-12-09 15:45:29 +00:00
} {
if typ := influxql . InspectDataType ( tt . v ) ; tt . typ != typ {
t . Errorf ( "%d. %v (%s): unexpected type: %s" , i , tt . v , tt . typ , typ )
continue
}
}
}
2016-01-23 04:22:50 +00:00
func TestDataType_String ( t * testing . T ) {
for i , tt := range [ ] struct {
typ influxql . DataType
v string
} {
{ influxql . Float , "float" } ,
{ influxql . Integer , "integer" } ,
{ influxql . Boolean , "boolean" } ,
{ influxql . String , "string" } ,
{ influxql . Time , "time" } ,
{ influxql . Duration , "duration" } ,
2016-05-16 16:08:28 +00:00
{ influxql . Tag , "tag" } ,
2016-01-23 04:22:50 +00:00
{ influxql . Unknown , "unknown" } ,
} {
if v := tt . typ . String ( ) ; tt . v != v {
t . Errorf ( "%d. %v (%s): unexpected string: %s" , i , tt . typ , tt . v , v )
}
}
}
2015-01-20 02:44:47 +00:00
// Ensure the SELECT statement can extract GROUP BY interval.
func TestSelectStatement_GroupByInterval ( t * testing . T ) {
2015-05-11 23:46:25 +00:00
q := "SELECT sum(value) from foo where time < now() GROUP BY time(10m)"
2015-01-20 02:44:47 +00:00
stmt , err := influxql . NewParser ( strings . NewReader ( q ) ) . ParseStatement ( )
if err != nil {
t . Fatalf ( "invalid statement: %q: %s" , stmt , err )
}
s := stmt . ( * influxql . SelectStatement )
d , err := s . GroupByInterval ( )
if d != 10 * time . Minute {
t . Fatalf ( "group by interval not equal:\nexp=%s\ngot=%s" , 10 * time . Minute , d )
}
if err != nil {
t . Fatalf ( "error parsing group by interval: %s" , err . Error ( ) )
}
}
2015-06-28 06:54:34 +00:00
// Ensure the SELECT statement can have its start and end time set
2015-01-22 04:40:03 +00:00
func TestSelectStatement_SetTimeRange ( t * testing . T ) {
2015-05-11 23:46:25 +00:00
q := "SELECT sum(value) from foo where time < now() GROUP BY time(10m)"
2015-01-22 04:40:03 +00:00
stmt , err := influxql . NewParser ( strings . NewReader ( q ) ) . ParseStatement ( )
if err != nil {
t . Fatalf ( "invalid statement: %q: %s" , stmt , err )
}
s := stmt . ( * influxql . SelectStatement )
start := time . Now ( ) . Add ( - 20 * time . Hour ) . Round ( time . Second ) . UTC ( )
end := time . Now ( ) . Add ( 10 * time . Hour ) . Round ( time . Second ) . UTC ( )
s . SetTimeRange ( start , end )
2016-03-28 17:26:13 +00:00
min , max := MustTimeRange ( s . Condition )
2015-01-22 04:40:03 +00:00
if min != start {
t . Fatalf ( "start time wasn't set properly.\n exp: %s\n got: %s" , start , min )
}
2015-07-29 19:33:03 +00:00
// the end range is actually one nanosecond before the given one since end is exclusive
end = end . Add ( - time . Nanosecond )
2015-01-22 04:40:03 +00:00
if max != end {
t . Fatalf ( "end time wasn't set properly.\n exp: %s\n got: %s" , end , max )
}
// ensure we can set a time on a select that already has one set
start = time . Now ( ) . Add ( - 20 * time . Hour ) . Round ( time . Second ) . UTC ( )
end = time . Now ( ) . Add ( 10 * time . Hour ) . Round ( time . Second ) . UTC ( )
q = fmt . Sprintf ( "SELECT sum(value) from foo WHERE time >= %ds and time <= %ds GROUP BY time(10m)" , start . Unix ( ) , end . Unix ( ) )
stmt , err = influxql . NewParser ( strings . NewReader ( q ) ) . ParseStatement ( )
if err != nil {
t . Fatalf ( "invalid statement: %q: %s" , stmt , err )
}
s = stmt . ( * influxql . SelectStatement )
2016-03-28 17:26:13 +00:00
min , max = MustTimeRange ( s . Condition )
2015-01-22 04:40:03 +00:00
if start != min || end != max {
t . Fatalf ( "start and end times weren't equal:\n exp: %s\n got: %s\n exp: %s\n got:%s\n" , start , min , end , max )
}
// update and ensure it saves it
start = time . Now ( ) . Add ( - 40 * time . Hour ) . Round ( time . Second ) . UTC ( )
end = time . Now ( ) . Add ( 20 * time . Hour ) . Round ( time . Second ) . UTC ( )
s . SetTimeRange ( start , end )
2016-03-28 17:26:13 +00:00
min , max = MustTimeRange ( s . Condition )
2015-01-22 04:40:03 +00:00
// TODO: right now the SetTimeRange can't override the start time if it's more recent than what they're trying to set it to.
// shouldn't matter for our purposes with continuous queries, but fix this later
if min != start {
t . Fatalf ( "start time wasn't set properly.\n exp: %s\n got: %s" , start , min )
}
2015-07-29 19:33:03 +00:00
// the end range is actually one nanosecond before the given one since end is exclusive
end = end . Add ( - time . Nanosecond )
2015-01-22 04:40:03 +00:00
if max != end {
t . Fatalf ( "end time wasn't set properly.\n exp: %s\n got: %s" , end , max )
}
// ensure that when we set a time range other where clause conditions are still there
2015-05-11 23:46:25 +00:00
q = "SELECT sum(value) from foo WHERE foo = 'bar' and time < now() GROUP BY time(10m)"
2015-01-22 04:40:03 +00:00
stmt , err = influxql . NewParser ( strings . NewReader ( q ) ) . ParseStatement ( )
if err != nil {
t . Fatalf ( "invalid statement: %q: %s" , stmt , err )
}
s = stmt . ( * influxql . SelectStatement )
// update and ensure it saves it
start = time . Now ( ) . Add ( - 40 * time . Hour ) . Round ( time . Second ) . UTC ( )
end = time . Now ( ) . Add ( 20 * time . Hour ) . Round ( time . Second ) . UTC ( )
s . SetTimeRange ( start , end )
2016-03-28 17:26:13 +00:00
min , max = MustTimeRange ( s . Condition )
2015-01-22 04:40:03 +00:00
if min != start {
t . Fatalf ( "start time wasn't set properly.\n exp: %s\n got: %s" , start , min )
}
2015-07-29 19:33:03 +00:00
// the end range is actually one nanosecond before the given one since end is exclusive
end = end . Add ( - time . Nanosecond )
2015-01-22 04:40:03 +00:00
if max != end {
t . Fatalf ( "end time wasn't set properly.\n exp: %s\n got: %s" , end , max )
}
// ensure the where clause is there
hasWhere := false
influxql . WalkFunc ( s . Condition , func ( n influxql . Node ) {
if ex , ok := n . ( * influxql . BinaryExpr ) ; ok {
if lhs , ok := ex . LHS . ( * influxql . VarRef ) ; ok {
if lhs . Val == "foo" {
if rhs , ok := ex . RHS . ( * influxql . StringLiteral ) ; ok {
if rhs . Val == "bar" {
hasWhere = true
}
}
}
}
}
} )
if ! hasWhere {
t . Fatal ( "set time range cleared out the where clause" )
}
}
2015-03-06 19:23:58 +00:00
// Ensure the idents from the select clause can come out
func TestSelect_NamesInSelect ( t * testing . T ) {
2015-08-11 19:03:17 +00:00
s := MustParseSelectStatement ( "select count(asdf), count(bar) from cpu" )
2015-03-06 19:23:58 +00:00
a := s . NamesInSelect ( )
if ! reflect . DeepEqual ( a , [ ] string { "asdf" , "bar" } ) {
t . Fatal ( "expected names asdf and bar" )
}
}
// Ensure the idents from the where clause can come out
func TestSelect_NamesInWhere ( t * testing . T ) {
s := MustParseSelectStatement ( "select * from cpu where time > 23s AND (asdf = 'jkl' OR (foo = 'bar' AND baz = 'bar'))" )
a := s . NamesInWhere ( )
if ! reflect . DeepEqual ( a , [ ] string { "time" , "asdf" , "foo" , "baz" } ) {
2015-03-09 18:17:36 +00:00
t . Fatalf ( "exp: time,asdf,foo,baz\ngot: %s\n" , strings . Join ( a , "," ) )
2015-03-06 19:23:58 +00:00
}
}
2015-02-18 16:47:59 +00:00
func TestSelectStatement_HasWildcard ( t * testing . T ) {
var tests = [ ] struct {
stmt string
wildcard bool
} {
// No wildcards
{
stmt : ` SELECT value FROM cpu ` ,
wildcard : false ,
} ,
// Query wildcard
{
stmt : ` SELECT * FROM cpu ` ,
wildcard : true ,
} ,
// No GROUP BY wildcards
{
stmt : ` SELECT value FROM cpu GROUP BY host ` ,
wildcard : false ,
} ,
// No GROUP BY wildcards, time only
{
2015-05-11 23:46:25 +00:00
stmt : ` SELECT mean(value) FROM cpu where time < now() GROUP BY time(5ms) ` ,
2015-02-18 16:47:59 +00:00
wildcard : false ,
} ,
// GROUP BY wildcard
{
stmt : ` SELECT value FROM cpu GROUP BY * ` ,
wildcard : true ,
} ,
// GROUP BY wildcard with time
{
2015-05-11 23:46:25 +00:00
stmt : ` SELECT mean(value) FROM cpu where time < now() GROUP BY *,time(1m) ` ,
2015-02-18 16:47:59 +00:00
wildcard : true ,
} ,
// GROUP BY wildcard with explicit
{
stmt : ` SELECT value FROM cpu GROUP BY *,host ` ,
wildcard : true ,
} ,
// GROUP BY multiple wildcards
{
stmt : ` SELECT value FROM cpu GROUP BY *,* ` ,
wildcard : true ,
} ,
// Combo
{
stmt : ` SELECT * FROM cpu GROUP BY * ` ,
wildcard : true ,
} ,
}
for i , tt := range tests {
// Parse statement.
stmt , err := influxql . NewParser ( strings . NewReader ( tt . stmt ) ) . ParseStatement ( )
if err != nil {
t . Fatalf ( "invalid statement: %q: %s" , tt . stmt , err )
}
// Test wildcard detection.
if w := stmt . ( * influxql . SelectStatement ) . HasWildcard ( ) ; tt . wildcard != w {
t . Errorf ( "%d. %q: unexpected wildcard detection:\n\nexp=%v\n\ngot=%v\n\n" , i , tt . stmt , tt . wildcard , w )
continue
}
}
}
2016-05-16 16:08:28 +00:00
// Test SELECT statement field rewrite.
func TestSelectStatement_RewriteFields ( t * testing . T ) {
2015-02-18 06:02:03 +00:00
var tests = [ ] struct {
stmt string
rewrite string
} {
// No wildcards
{
stmt : ` SELECT value FROM cpu ` ,
rewrite : ` SELECT value FROM cpu ` ,
} ,
// Query wildcard
{
stmt : ` SELECT * FROM cpu ` ,
2016-05-16 16:08:28 +00:00
rewrite : ` SELECT host::tag, region::tag, value1::float, value2::integer FROM cpu ` ,
2015-02-18 06:02:03 +00:00
} ,
// Parser fundamentally prohibits multiple query sources
// Query wildcard with explicit
2016-02-23 15:16:55 +00:00
{
stmt : ` SELECT *,value1 FROM cpu ` ,
2016-05-16 16:08:28 +00:00
rewrite : ` SELECT host::tag, region::tag, value1::float, value2::integer, value1::float FROM cpu ` ,
2016-02-23 15:16:55 +00:00
} ,
2015-02-18 06:02:03 +00:00
// Query multiple wildcards
2016-02-23 15:16:55 +00:00
{
stmt : ` SELECT *,* FROM cpu ` ,
2016-05-16 16:08:28 +00:00
rewrite : ` SELECT host::tag, region::tag, value1::float, value2::integer, host::tag, region::tag, value1::float, value2::integer FROM cpu ` ,
2016-02-23 15:16:55 +00:00
} ,
// Query wildcards with group by
{
stmt : ` SELECT * FROM cpu GROUP BY host ` ,
2016-05-16 16:08:28 +00:00
rewrite : ` SELECT region::tag, value1::float, value2::integer FROM cpu GROUP BY host ` ,
2016-02-23 15:16:55 +00:00
} ,
2015-02-18 06:02:03 +00:00
// No GROUP BY wildcards
{
stmt : ` SELECT value FROM cpu GROUP BY host ` ,
rewrite : ` SELECT value FROM cpu GROUP BY host ` ,
} ,
2015-02-18 07:04:46 +00:00
// No GROUP BY wildcards, time only
{
2015-05-11 23:46:25 +00:00
stmt : ` SELECT mean(value) FROM cpu where time < now() GROUP BY time(5ms) ` ,
rewrite : ` SELECT mean(value) FROM cpu WHERE time < now() GROUP BY time(5ms) ` ,
2015-02-18 07:04:46 +00:00
} ,
2015-02-18 06:02:03 +00:00
// GROUP BY wildcard
{
stmt : ` SELECT value FROM cpu GROUP BY * ` ,
rewrite : ` SELECT value FROM cpu GROUP BY host, region ` ,
} ,
// GROUP BY wildcard with time
{
2015-05-11 23:46:25 +00:00
stmt : ` SELECT mean(value) FROM cpu where time < now() GROUP BY *,time(1m) ` ,
rewrite : ` SELECT mean(value) FROM cpu WHERE time < now() GROUP BY host, region, time(1m) ` ,
2015-02-18 06:02:03 +00:00
} ,
2016-05-16 16:08:28 +00:00
// GROUP BY wildcard with fill
2015-03-12 02:29:18 +00:00
{
2015-05-11 23:46:25 +00:00
stmt : ` SELECT mean(value) FROM cpu where time < now() GROUP BY *,time(1m) fill(0) ` ,
rewrite : ` SELECT mean(value) FROM cpu WHERE time < now() GROUP BY host, region, time(1m) fill(0) ` ,
2015-03-12 02:29:18 +00:00
} ,
2015-02-18 06:02:03 +00:00
// GROUP BY wildcard with explicit
{
stmt : ` SELECT value FROM cpu GROUP BY *,host ` ,
rewrite : ` SELECT value FROM cpu GROUP BY host, region, host ` ,
} ,
// GROUP BY multiple wildcards
{
stmt : ` SELECT value FROM cpu GROUP BY *,* ` ,
rewrite : ` SELECT value FROM cpu GROUP BY host, region, host, region ` ,
} ,
// Combo
{
stmt : ` SELECT * FROM cpu GROUP BY * ` ,
2016-05-16 16:08:28 +00:00
rewrite : ` SELECT value1::float, value2::integer FROM cpu GROUP BY host, region ` ,
2015-02-18 06:02:03 +00:00
} ,
2016-07-28 22:56:23 +00:00
// Wildcard function with all fields.
{
stmt : ` SELECT mean(*) FROM cpu ` ,
rewrite : ` SELECT mean(value1::float) AS mean_value1, mean(value2::integer) AS mean_value2 FROM cpu ` ,
} ,
{
stmt : ` SELECT distinct(*) FROM strings ` ,
rewrite : ` SELECT distinct(string::string) AS distinct_string, distinct(value::float) AS distinct_value FROM strings ` ,
} ,
{
stmt : ` SELECT distinct(*) FROM bools ` ,
rewrite : ` SELECT distinct(bool::boolean) AS distinct_bool, distinct(value::float) AS distinct_value FROM bools ` ,
} ,
// Wildcard function with some fields excluded.
{
stmt : ` SELECT mean(*) FROM strings ` ,
rewrite : ` SELECT mean(value::float) AS mean_value FROM strings ` ,
} ,
{
stmt : ` SELECT mean(*) FROM bools ` ,
rewrite : ` SELECT mean(value::float) AS mean_value FROM bools ` ,
} ,
// Wildcard function with an alias.
{
stmt : ` SELECT mean(*) AS alias FROM cpu ` ,
rewrite : ` SELECT mean(value1::float) AS alias_value1, mean(value2::integer) AS alias_value2 FROM cpu ` ,
} ,
2016-10-10 19:39:46 +00:00
// Query regex
{
stmt : ` SELECT /1/ FROM cpu ` ,
rewrite : ` SELECT value1::float FROM cpu ` ,
} ,
{
stmt : ` SELECT value1 FROM cpu GROUP BY /h/ ` ,
rewrite : ` SELECT value1::float FROM cpu GROUP BY host ` ,
} ,
// Query regex
{
stmt : ` SELECT mean(/1/) FROM cpu ` ,
rewrite : ` SELECT mean(value1::float) AS mean_value1 FROM cpu ` ,
} ,
2015-02-18 06:02:03 +00:00
}
for i , tt := range tests {
// Parse statement.
stmt , err := influxql . NewParser ( strings . NewReader ( tt . stmt ) ) . ParseStatement ( )
if err != nil {
t . Fatalf ( "invalid statement: %q: %s" , tt . stmt , err )
}
2015-11-04 21:06:06 +00:00
var ic IteratorCreator
2016-05-16 16:08:28 +00:00
ic . FieldDimensionsFn = func ( sources influxql . Sources ) ( fields map [ string ] influxql . DataType , dimensions map [ string ] struct { } , err error ) {
2016-07-28 22:56:23 +00:00
source := sources [ 0 ] . ( * influxql . Measurement )
switch source . Name {
case "cpu" :
fields = map [ string ] influxql . DataType {
"value1" : influxql . Float ,
"value2" : influxql . Integer ,
}
case "strings" :
fields = map [ string ] influxql . DataType {
"value" : influxql . Float ,
"string" : influxql . String ,
}
case "bools" :
fields = map [ string ] influxql . DataType {
"value" : influxql . Float ,
"bool" : influxql . Boolean ,
}
}
2015-11-04 21:06:06 +00:00
dimensions = map [ string ] struct { } { "host" : struct { } { } , "region" : struct { } { } }
return
}
2015-02-18 16:48:30 +00:00
// Rewrite statement.
2016-05-16 16:08:28 +00:00
rw , err := stmt . ( * influxql . SelectStatement ) . RewriteFields ( & ic )
2015-11-04 21:06:06 +00:00
if err != nil {
t . Errorf ( "%d. %q: error: %s" , i , tt . stmt , err )
} else if rw == nil {
2015-02-18 06:02:03 +00:00
t . Errorf ( "%d. %q: unexpected nil statement" , i , tt . stmt )
2015-11-04 21:06:06 +00:00
} else if rw := rw . String ( ) ; tt . rewrite != rw {
2015-02-18 06:02:03 +00:00
t . Errorf ( "%d. %q: unexpected rewrite:\n\nexp=%s\n\ngot=%s\n\n" , i , tt . stmt , tt . rewrite , rw )
}
}
}
2016-10-20 16:18:04 +00:00
// Test SELECT statement regex conditions rewrite.
func TestSelectStatement_RewriteRegexConditions ( t * testing . T ) {
var tests = [ ] struct {
in string
out string
} {
{ in : ` SELECT value FROM cpu ` , out : ` SELECT value FROM cpu ` } ,
{ in : ` SELECT value FROM cpu WHERE host='server-1' ` , out : ` SELECT value FROM cpu WHERE host='server-1' ` } ,
{ in : ` SELECT value FROM cpu WHERE host = 'server-1' ` , out : ` SELECT value FROM cpu WHERE host = 'server-1' ` } ,
{ in : ` SELECT value FROM cpu WHERE host != 'server-1' ` , out : ` SELECT value FROM cpu WHERE host != 'server-1' ` } ,
// Non matching regex
{ in : ` SELECT value FROM cpu WHERE host =~ /server-1|server-2|server-3/ ` , out : ` SELECT value FROM cpu WHERE host =~ /server-1|server-2|server-3/ ` } ,
{ in : ` SELECT value FROM cpu WHERE host =~ /server-1/ ` , out : ` SELECT value FROM cpu WHERE host =~ /server-1/ ` } ,
{ in : ` SELECT value FROM cpu WHERE host !~ /server-1/ ` , out : ` SELECT value FROM cpu WHERE host !~ /server-1/ ` } ,
{ in : ` SELECT value FROM cpu WHERE host =~ /^server-1/ ` , out : ` SELECT value FROM cpu WHERE host =~ /^server-1/ ` } ,
{ in : ` SELECT value FROM cpu WHERE host =~ /server-1$/ ` , out : ` SELECT value FROM cpu WHERE host =~ /server-1$/ ` } ,
{ in : ` SELECT value FROM cpu WHERE host !~ /\^server-1$/ ` , out : ` SELECT value FROM cpu WHERE host !~ /\^server-1$/ ` } ,
{ in : ` SELECT value FROM cpu WHERE host !~ /\^$/ ` , out : ` SELECT value FROM cpu WHERE host !~ /\^$/ ` } ,
{ in : ` SELECT value FROM cpu WHERE host !~ /^server-1\$/ ` , out : ` SELECT value FROM cpu WHERE host !~ /^server-1\$/ ` } ,
{ in : ` SELECT value FROM cpu WHERE host =~ /^\$/ ` , out : ` SELECT value FROM cpu WHERE host =~ /^\$/ ` } ,
{ in : ` SELECT value FROM cpu WHERE host !~ /^a/ ` , out : ` SELECT value FROM cpu WHERE host !~ /^a/ ` } ,
// These regexes are not supported due to the presence of escaped or meta characters.
{ in : ` SELECT value FROM cpu WHERE host !~ /^(foo|bar)$/ ` , out : ` SELECT value FROM cpu WHERE host !~ /^(foo|bar)$/ ` } ,
{ in : ` SELECT value FROM cpu WHERE host !~ /^?a$/ ` , out : ` SELECT value FROM cpu WHERE host !~ /^?a$/ ` } ,
{ in : ` SELECT value FROM cpu WHERE host !~ /^[a-z]$/ ` , out : ` SELECT value FROM cpu WHERE host !~ /^[a-z]$/ ` } ,
{ in : ` SELECT value FROM cpu WHERE host !~ /^\d$/ ` , out : ` SELECT value FROM cpu WHERE host !~ /^\d$/ ` } ,
{ in : ` SELECT value FROM cpu WHERE host !~ /^a*$/ ` , out : ` SELECT value FROM cpu WHERE host !~ /^a*$/ ` } ,
{ in : ` SELECT value FROM cpu WHERE host !~ /^a.b$/ ` , out : ` SELECT value FROM cpu WHERE host !~ /^a.b$/ ` } ,
{ in : ` SELECT value FROM cpu WHERE host !~ /^ab+$/ ` , out : ` SELECT value FROM cpu WHERE host !~ /^ab+$/ ` } ,
{ in : ` SELECT value FROM cpu WHERE host =~ /^hello\world$/ ` , out : ` SELECT value FROM cpu WHERE host =~ /^hello\world$/ ` } ,
// These regexes all match and will be rewritten.
{ in : ` SELECT value FROM cpu WHERE host !~ /^a[2]$/ ` , out : ` SELECT value FROM cpu WHERE host != 'a2' ` } ,
{ in : ` SELECT value FROM cpu WHERE host =~ /^server-1$/ ` , out : ` SELECT value FROM cpu WHERE host = 'server-1' ` } ,
{ in : ` SELECT value FROM cpu WHERE host !~ /^server-1$/ ` , out : ` SELECT value FROM cpu WHERE host != 'server-1' ` } ,
{ in : ` SELECT value FROM cpu WHERE host =~ /^server 1$/ ` , out : ` SELECT value FROM cpu WHERE host = 'server 1' ` } ,
{ in : ` SELECT value FROM cpu WHERE host =~ /^$/ ` , out : ` SELECT value FROM cpu WHERE host = '' ` } ,
{ in : ` SELECT value FROM cpu WHERE host !~ /^$/ ` , out : ` SELECT value FROM cpu WHERE host != '' ` } ,
{ in : ` SELECT value FROM cpu WHERE host =~ /^server-1$/ OR host =~ /^server-2$/ ` , out : ` SELECT value FROM cpu WHERE host = 'server-1' OR host = 'server-2' ` } ,
{ in : ` SELECT value FROM cpu WHERE host =~ /^server-1$/ OR host =~ /^server]a$/ ` , out : ` SELECT value FROM cpu WHERE host = 'server-1' OR host = 'server]a' ` } ,
{ in : ` SELECT value FROM cpu WHERE host =~ /^hello\?$/ ` , out : ` SELECT value FROM cpu WHERE host = 'hello?' ` } ,
{ in : ` SELECT value FROM cpu WHERE host !~ /^\\$/ ` , out : ` SELECT value FROM cpu WHERE host != '\\' ` } ,
{ in : ` SELECT value FROM cpu WHERE host !~ /^\\\$$/ ` , out : ` SELECT value FROM cpu WHERE host != '\\$' ` } ,
}
for i , test := range tests {
stmt , err := influxql . NewParser ( strings . NewReader ( test . in ) ) . ParseStatement ( )
if err != nil {
t . Fatalf ( "[Example %d], %v" , i , err )
}
// Rewrite any supported regex conditions.
stmt . ( * influxql . SelectStatement ) . RewriteRegexConditions ( )
// Get the expected rewritten statement.
expStmt , err := influxql . NewParser ( strings . NewReader ( test . out ) ) . ParseStatement ( )
if err != nil {
t . Fatalf ( "[Example %d], %v" , i , err )
}
// Compare the (potentially) rewritten AST to the expected AST.
if got , exp := stmt , expStmt ; ! reflect . DeepEqual ( got , exp ) {
t . Errorf ( "[Example %d]\nattempting %v\ngot %v\n%s\n\nexpected %v\n%s\n" , i + 1 , test . in , got , mustMarshalJSON ( got ) , exp , mustMarshalJSON ( exp ) )
}
}
}
2016-04-13 22:52:22 +00:00
// Test SELECT statement time field rewrite.
func TestSelectStatement_RewriteTimeFields ( t * testing . T ) {
var tests = [ ] struct {
s string
stmt influxql . Statement
} {
{
s : ` SELECT time, field1 FROM cpu ` ,
stmt : & influxql . SelectStatement {
IsRawQuery : true ,
Fields : [ ] * influxql . Field {
{ Expr : & influxql . VarRef { Val : "field1" } } ,
} ,
Sources : [ ] influxql . Source {
& influxql . Measurement { Name : "cpu" } ,
} ,
} ,
} ,
{
s : ` SELECT time AS timestamp, field1 FROM cpu ` ,
stmt : & influxql . SelectStatement {
IsRawQuery : true ,
Fields : [ ] * influxql . Field {
{ Expr : & influxql . VarRef { Val : "field1" } } ,
} ,
Sources : [ ] influxql . Source {
& influxql . Measurement { Name : "cpu" } ,
} ,
TimeAlias : "timestamp" ,
} ,
} ,
}
for i , tt := range tests {
// Parse statement.
stmt , err := influxql . NewParser ( strings . NewReader ( tt . s ) ) . ParseStatement ( )
if err != nil {
t . Fatalf ( "invalid statement: %q: %s" , tt . s , err )
}
// Rewrite statement.
stmt . ( * influxql . SelectStatement ) . RewriteTimeFields ( )
if ! reflect . DeepEqual ( tt . stmt , stmt ) {
t . Logf ( "\n# %s\nexp=%s\ngot=%s\n" , tt . s , mustMarshalJSON ( tt . stmt ) , mustMarshalJSON ( stmt ) )
t . Logf ( "\nSQL exp=%s\nSQL got=%s\n" , tt . stmt . String ( ) , stmt . String ( ) )
t . Errorf ( "%d. %q\n\nstmt mismatch:\n\nexp=%#v\n\ngot=%#v\n\n" , i , tt . s , tt . stmt , stmt )
}
}
}
2015-03-19 15:41:18 +00:00
// Ensure that the IsRawQuery flag gets set properly
func TestSelectStatement_IsRawQuerySet ( t * testing . T ) {
2015-03-19 22:12:46 +00:00
var tests = [ ] struct {
stmt string
isRaw bool
} {
{
stmt : "select * from foo" ,
isRaw : true ,
} ,
{
stmt : "select value1,value2 from foo" ,
isRaw : true ,
} ,
{
stmt : "select value1,value2 from foo, time(10m)" ,
isRaw : true ,
} ,
{
2015-05-11 23:46:25 +00:00
stmt : "select mean(value) from foo where time < now() group by time(5m)" ,
2015-03-19 22:12:46 +00:00
isRaw : false ,
} ,
{
stmt : "select mean(value) from foo group by bar" ,
isRaw : false ,
} ,
{
stmt : "select mean(value) from foo group by *" ,
isRaw : false ,
} ,
{
2015-08-31 21:04:53 +00:00
stmt : "select mean(value) from foo group by *" ,
2015-03-19 22:12:46 +00:00
isRaw : false ,
} ,
2015-03-19 15:41:18 +00:00
}
2015-03-19 22:12:46 +00:00
2015-11-04 21:06:06 +00:00
for _ , tt := range tests {
2015-03-19 22:12:46 +00:00
s := MustParseSelectStatement ( tt . stmt )
if s . IsRawQuery != tt . isRaw {
t . Errorf ( "'%s', IsRawQuery should be %v" , tt . stmt , tt . isRaw )
}
2015-03-19 15:41:18 +00:00
}
}
2015-12-07 22:20:14 +00:00
func TestSelectStatement_HasDerivative ( t * testing . T ) {
var tests = [ ] struct {
stmt string
derivative bool
} {
// No derivatives
{
stmt : ` SELECT value FROM cpu ` ,
derivative : false ,
} ,
// Query derivative
{
stmt : ` SELECT derivative(value) FROM cpu ` ,
derivative : true ,
} ,
// No GROUP BY time only
{
stmt : ` SELECT mean(value) FROM cpu where time < now() GROUP BY time(5ms) ` ,
derivative : false ,
} ,
// No GROUP BY derivatives, time only
{
stmt : ` SELECT derivative(mean(value)) FROM cpu where time < now() GROUP BY time(5ms) ` ,
derivative : true ,
} ,
{
stmt : ` SELECT value FROM cpu ` ,
derivative : false ,
} ,
// Query derivative
{
stmt : ` SELECT non_negative_derivative(value) FROM cpu ` ,
derivative : true ,
} ,
// No GROUP BY derivatives, time only
{
stmt : ` SELECT non_negative_derivative(mean(value)) FROM cpu where time < now() GROUP BY time(5ms) ` ,
derivative : true ,
} ,
// Invalid derivative function name
{
stmt : ` SELECT typoDerivative(value) FROM cpu where time < now() ` ,
derivative : false ,
} ,
}
for i , tt := range tests {
// Parse statement.
t . Logf ( "index: %d, statement: %s" , i , tt . stmt )
stmt , err := influxql . NewParser ( strings . NewReader ( tt . stmt ) ) . ParseStatement ( )
if err != nil {
t . Fatalf ( "invalid statement: %q: %s" , tt . stmt , err )
}
// Test derivative detection.
if d := stmt . ( * influxql . SelectStatement ) . HasDerivative ( ) ; tt . derivative != d {
t . Errorf ( "%d. %q: unexpected derivative detection:\n\nexp=%v\n\ngot=%v\n\n" , i , tt . stmt , tt . derivative , d )
continue
}
}
}
func TestSelectStatement_IsSimpleDerivative ( t * testing . T ) {
var tests = [ ] struct {
stmt string
derivative bool
} {
// No derivatives
{
stmt : ` SELECT value FROM cpu ` ,
derivative : false ,
} ,
// Query derivative
{
stmt : ` SELECT derivative(value) FROM cpu ` ,
derivative : true ,
} ,
// Query derivative
{
stmt : ` SELECT non_negative_derivative(value) FROM cpu ` ,
derivative : true ,
} ,
// No GROUP BY time only
{
stmt : ` SELECT mean(value) FROM cpu where time < now() GROUP BY time(5ms) ` ,
derivative : false ,
} ,
// No GROUP BY derivatives, time only
{
stmt : ` SELECT non_negative_derivative(mean(value)) FROM cpu where time < now() GROUP BY time(5ms) ` ,
derivative : false ,
} ,
// Invalid derivative function name
{
stmt : ` SELECT typoDerivative(value) FROM cpu where time < now() ` ,
derivative : false ,
} ,
}
for i , tt := range tests {
// Parse statement.
t . Logf ( "index: %d, statement: %s" , i , tt . stmt )
stmt , err := influxql . NewParser ( strings . NewReader ( tt . stmt ) ) . ParseStatement ( )
if err != nil {
t . Fatalf ( "invalid statement: %q: %s" , tt . stmt , err )
}
// Test derivative detection.
if d := stmt . ( * influxql . SelectStatement ) . IsSimpleDerivative ( ) ; tt . derivative != d {
t . Errorf ( "%d. %q: unexpected derivative detection:\n\nexp=%v\n\ngot=%v\n\n" , i , tt . stmt , tt . derivative , d )
continue
}
}
}
2016-02-18 00:24:13 +00:00
// Ensure binary expression names can be evaluated.
func TestBinaryExprName ( t * testing . T ) {
for i , tt := range [ ] struct {
expr string
name string
} {
{ expr : ` value + 1 ` , name : ` value ` } ,
{ expr : ` "user" / total ` , name : ` user_total ` } ,
{ expr : ` ("user" + total) / total ` , name : ` user_total_total ` } ,
} {
expr := influxql . MustParseExpr ( tt . expr )
switch expr := expr . ( type ) {
case * influxql . BinaryExpr :
name := influxql . BinaryExprName ( expr )
if name != tt . name {
t . Errorf ( "%d. unexpected name %s, got %s" , i , name , tt . name )
}
default :
t . Errorf ( "%d. unexpected expr type: %T" , i , expr )
}
}
}
2014-12-15 15:34:32 +00:00
// Ensure the time range of an expression can be extracted.
func TestTimeRange ( t * testing . T ) {
for i , tt := range [ ] struct {
2016-03-28 17:26:13 +00:00
expr string
min , max , err string
2014-12-15 15:34:32 +00:00
} {
// LHS VarRef
2015-07-29 19:33:03 +00:00
{ expr : ` time > '2000-01-01 00:00:00' ` , min : ` 2000-01-01T00:00:00.000000001Z ` , max : ` 0001-01-01T00:00:00Z ` } ,
{ expr : ` time >= '2000-01-01 00:00:00' ` , min : ` 2000-01-01T00:00:00Z ` , max : ` 0001-01-01T00:00:00Z ` } ,
{ expr : ` time < '2000-01-01 00:00:00' ` , min : ` 0001-01-01T00:00:00Z ` , max : ` 1999-12-31T23:59:59.999999999Z ` } ,
{ expr : ` time <= '2000-01-01 00:00:00' ` , min : ` 0001-01-01T00:00:00Z ` , max : ` 2000-01-01T00:00:00Z ` } ,
2014-12-15 15:34:32 +00:00
// RHS VarRef
2015-07-29 19:33:03 +00:00
{ expr : ` '2000-01-01 00:00:00' > time ` , min : ` 0001-01-01T00:00:00Z ` , max : ` 1999-12-31T23:59:59.999999999Z ` } ,
{ expr : ` '2000-01-01 00:00:00' >= time ` , min : ` 0001-01-01T00:00:00Z ` , max : ` 2000-01-01T00:00:00Z ` } ,
{ expr : ` '2000-01-01 00:00:00' < time ` , min : ` 2000-01-01T00:00:00.000000001Z ` , max : ` 0001-01-01T00:00:00Z ` } ,
{ expr : ` '2000-01-01 00:00:00' <= time ` , min : ` 2000-01-01T00:00:00Z ` , max : ` 0001-01-01T00:00:00Z ` } ,
2014-12-15 15:34:32 +00:00
2015-08-18 21:17:18 +00:00
// number literal
{ expr : ` time < 10 ` , min : ` 0001-01-01T00:00:00Z ` , max : ` 1970-01-01T00:00:00.000000009Z ` } ,
2014-12-15 15:34:32 +00:00
// Equality
2016-02-08 21:07:37 +00:00
{ expr : ` time = '2000-01-01 00:00:00' ` , min : ` 2000-01-01T00:00:00Z ` , max : ` 2000-01-01T00:00:00.000000001Z ` } ,
2014-12-15 15:34:32 +00:00
// Multiple time expressions.
2015-07-29 19:33:03 +00:00
{ expr : ` time >= '2000-01-01 00:00:00' AND time < '2000-01-02 00:00:00' ` , min : ` 2000-01-01T00:00:00Z ` , max : ` 2000-01-01T23:59:59.999999999Z ` } ,
2014-12-15 15:34:32 +00:00
2014-12-16 15:57:27 +00:00
// Min/max crossover
2015-07-29 19:33:03 +00:00
{ expr : ` time >= '2000-01-01 00:00:00' AND time <= '1999-01-01 00:00:00' ` , min : ` 2000-01-01T00:00:00Z ` , max : ` 1999-01-01T00:00:00Z ` } ,
2014-12-16 15:57:27 +00:00
2014-12-21 18:07:14 +00:00
// Absolute time
2016-02-08 21:07:37 +00:00
{ expr : ` time = 1388534400s ` , min : ` 2014-01-01T00:00:00Z ` , max : ` 2014-01-01T00:00:00.000000001Z ` } ,
2014-12-21 18:07:14 +00:00
2014-12-15 15:34:32 +00:00
// Non-comparative expressions.
2015-07-29 19:33:03 +00:00
{ expr : ` time ` , min : ` 0001-01-01T00:00:00Z ` , max : ` 0001-01-01T00:00:00Z ` } ,
{ expr : ` time + 2 ` , min : ` 0001-01-01T00:00:00Z ` , max : ` 0001-01-01T00:00:00Z ` } ,
{ expr : ` time - '2000-01-01 00:00:00' ` , min : ` 0001-01-01T00:00:00Z ` , max : ` 0001-01-01T00:00:00Z ` } ,
{ expr : ` time AND '2000-01-01 00:00:00' ` , min : ` 0001-01-01T00:00:00Z ` , max : ` 0001-01-01T00:00:00Z ` } ,
2016-03-28 17:26:13 +00:00
// Invalid time expressions.
{ expr : ` time > "2000-01-01 00:00:00" ` , min : ` 0001-01-01T00:00:00Z ` , max : ` 0001-01-01T00:00:00Z ` , err : ` invalid operation: time and *influxql.VarRef are not compatible ` } ,
2016-05-19 15:37:15 +00:00
{ expr : ` time > '2262-04-11 23:47:17' ` , min : ` 0001-01-01T00:00:00Z ` , max : ` 0001-01-01T00:00:00Z ` , err : ` time 2262-04-11T23:47:17Z overflows time literal ` } ,
2016-04-08 19:51:32 +00:00
{ expr : ` time > '1677-09-20 19:12:43' ` , min : ` 0001-01-01T00:00:00Z ` , max : ` 0001-01-01T00:00:00Z ` , err : ` time 1677-09-20T19:12:43Z underflows time literal ` } ,
2014-12-15 15:34:32 +00:00
} {
// Extract time range.
expr := MustParseExpr ( tt . expr )
2016-03-28 17:26:13 +00:00
min , max , err := influxql . TimeRange ( expr )
2014-12-15 15:34:32 +00:00
// Compare with expected min/max.
2015-07-29 19:33:03 +00:00
if min := min . Format ( time . RFC3339Nano ) ; tt . min != min {
2014-12-15 15:34:32 +00:00
t . Errorf ( "%d. %s: unexpected min:\n\nexp=%s\n\ngot=%s\n\n" , i , tt . expr , tt . min , min )
continue
}
2015-07-29 19:33:03 +00:00
if max := max . Format ( time . RFC3339Nano ) ; tt . max != max {
2014-12-15 15:34:32 +00:00
t . Errorf ( "%d. %s: unexpected max:\n\nexp=%s\n\ngot=%s\n\n" , i , tt . expr , tt . max , max )
continue
}
2016-03-28 17:26:13 +00:00
if ( err != nil && err . Error ( ) != tt . err ) || ( err == nil && tt . err != "" ) {
t . Errorf ( "%d. %s: unexpected error:\n\nexp=%s\n\ngot=%s\n\n" , i , tt . expr , tt . err , err )
}
2014-12-15 15:34:32 +00:00
}
}
2014-12-21 22:18:55 +00:00
2015-02-05 06:29:19 +00:00
// Ensure that we see if a where clause has only time limitations
2015-10-08 00:07:43 +00:00
func TestOnlyTimeExpr ( t * testing . T ) {
2015-02-05 06:29:19 +00:00
var tests = [ ] struct {
stmt string
exp bool
} {
{
stmt : ` SELECT value FROM myseries WHERE value > 1 ` ,
exp : false ,
} ,
{
2015-02-08 11:06:30 +00:00
stmt : ` SELECT value FROM foo WHERE time >= '2000-01-01T00:00:05Z' ` ,
2015-02-05 06:29:19 +00:00
exp : true ,
} ,
{
2015-02-08 11:06:30 +00:00
stmt : ` SELECT value FROM foo WHERE time >= '2000-01-01T00:00:05Z' AND time < '2000-01-01T00:00:05Z' ` ,
2015-02-05 06:29:19 +00:00
exp : true ,
} ,
{
2015-02-08 11:06:30 +00:00
stmt : ` SELECT value FROM foo WHERE time >= '2000-01-01T00:00:05Z' AND asdf = 'bar' ` ,
2015-02-05 06:29:19 +00:00
exp : false ,
} ,
{
2015-02-08 11:06:30 +00:00
stmt : ` SELECT value FROM foo WHERE asdf = 'jkl' AND (time >= '2000-01-01T00:00:05Z' AND time < '2000-01-01T00:00:05Z') ` ,
2015-02-05 06:29:19 +00:00
exp : false ,
} ,
}
for i , tt := range tests {
// Parse statement.
stmt , err := influxql . NewParser ( strings . NewReader ( tt . stmt ) ) . ParseStatement ( )
if err != nil {
t . Fatalf ( "invalid statement: %q: %s" , tt . stmt , err )
}
2015-10-08 00:07:43 +00:00
if influxql . OnlyTimeExpr ( stmt . ( * influxql . SelectStatement ) . Condition ) != tt . exp {
2015-02-05 06:29:19 +00:00
t . Fatalf ( "%d. expected statement to return only time dimension to be %t: %s" , i , tt . exp , tt . stmt )
}
}
}
2014-12-21 22:18:55 +00:00
// Ensure an AST node can be rewritten.
func TestRewrite ( t * testing . T ) {
expr := MustParseExpr ( ` time > 1 OR foo = 2 ` )
// Flip LHS & RHS in all binary expressions.
act := influxql . RewriteFunc ( expr , func ( n influxql . Node ) influxql . Node {
switch n := n . ( type ) {
case * influxql . BinaryExpr :
return & influxql . BinaryExpr { Op : n . Op , LHS : n . RHS , RHS : n . LHS }
default :
return n
}
} )
// Verify that everything is flipped.
2016-02-19 04:06:41 +00:00
if act := act . String ( ) ; act != ` 2 = foo OR 1 > time ` {
2014-12-21 22:18:55 +00:00
t . Fatalf ( "unexpected result: %s" , act )
}
}
2015-01-23 09:44:56 +00:00
2016-03-06 14:52:34 +00:00
// Ensure an Expr can be rewritten handling nils.
func TestRewriteExpr ( t * testing . T ) {
expr := MustParseExpr ( ` (time > 1 AND time < 10) OR foo = 2 ` )
// Remove all time expressions.
act := influxql . RewriteExpr ( expr , func ( e influxql . Expr ) influxql . Expr {
switch e := e . ( type ) {
case * influxql . BinaryExpr :
if lhs , ok := e . LHS . ( * influxql . VarRef ) ; ok && lhs . Val == "time" {
return nil
}
}
return e
} )
// Verify that everything is flipped.
2016-02-19 04:06:41 +00:00
if act := act . String ( ) ; act != ` foo = 2 ` {
2016-03-06 14:52:34 +00:00
t . Fatalf ( "unexpected result: %s" , act )
}
}
2015-07-22 19:49:17 +00:00
// Ensure that the String() value of a statement is parseable
func TestParseString ( t * testing . T ) {
var tests = [ ] struct {
stmt string
} {
{
stmt : ` SELECT "cpu load" FROM myseries ` ,
} ,
{
stmt : ` SELECT "cpu load" FROM "my series" ` ,
} ,
{
stmt : ` SELECT "cpu\"load" FROM myseries ` ,
} ,
{
stmt : ` SELECT "cpu'load" FROM myseries ` ,
} ,
{
stmt : ` SELECT "cpu load" FROM "my\"series" ` ,
} ,
2015-11-22 21:08:51 +00:00
{
stmt : ` SELECT "field with spaces" FROM "\"ugly\" db"."\"ugly\" rp"."\"ugly\" measurement" ` ,
} ,
2015-07-22 19:49:17 +00:00
{
stmt : ` SELECT * FROM myseries ` ,
} ,
2015-11-22 18:10:47 +00:00
{
stmt : ` DROP DATABASE "!" ` ,
} ,
{
stmt : ` DROP RETENTION POLICY "my rp" ON "a database" ` ,
} ,
{
stmt : ` CREATE RETENTION POLICY "my rp" ON "a database" DURATION 1d REPLICATION 1 ` ,
} ,
{
stmt : ` ALTER RETENTION POLICY "my rp" ON "a database" DEFAULT ` ,
} ,
{
stmt : ` SHOW RETENTION POLICIES ON "a database" ` ,
} ,
{
stmt : ` SHOW TAG VALUES WITH KEY IN ("a long name", short) ` ,
} ,
{
stmt : ` DROP CONTINUOUS QUERY "my query" ON "my database" ` ,
} ,
2016-02-10 17:26:18 +00:00
// See issues https://github.com/influxdata/influxdb/issues/1647
// and https://github.com/influxdata/influxdb/issues/4404
2015-12-04 21:44:42 +00:00
//{
// stmt: `DELETE FROM "my db"."my rp"."my measurement"`,
//},
2015-11-22 19:48:18 +00:00
{
stmt : ` DROP SUBSCRIPTION "ugly \"subscription\" name" ON "\"my\" db"."\"my\" rp" ` ,
} ,
{
stmt : ` CREATE SUBSCRIPTION "ugly \"subscription\" name" ON "\"my\" db"."\"my\" rp" DESTINATIONS ALL 'my host', 'my other host' ` ,
} ,
2015-11-22 20:38:48 +00:00
{
stmt : ` SHOW MEASUREMENTS WITH MEASUREMENT =~ /foo/ ` ,
} ,
{
stmt : ` SHOW MEASUREMENTS WITH MEASUREMENT = "and/or" ` ,
} ,
2015-11-22 21:08:51 +00:00
{
stmt : ` DROP USER "user with spaces" ` ,
} ,
{
stmt : ` GRANT ALL PRIVILEGES ON "db with spaces" TO "user with spaces" ` ,
} ,
{
stmt : ` GRANT ALL PRIVILEGES TO "user with spaces" ` ,
} ,
{
stmt : ` SHOW GRANTS FOR "user with spaces" ` ,
} ,
{
stmt : ` REVOKE ALL PRIVILEGES ON "db with spaces" FROM "user with spaces" ` ,
} ,
{
stmt : ` REVOKE ALL PRIVILEGES FROM "user with spaces" ` ,
} ,
{
stmt : ` CREATE DATABASE "db with spaces" ` ,
} ,
2015-07-22 19:49:17 +00:00
}
for _ , tt := range tests {
// Parse statement.
stmt , err := influxql . NewParser ( strings . NewReader ( tt . stmt ) ) . ParseStatement ( )
if err != nil {
t . Fatalf ( "invalid statement: %q: %s" , tt . stmt , err )
}
2015-11-22 20:38:48 +00:00
stmtCopy , err := influxql . NewParser ( strings . NewReader ( stmt . String ( ) ) ) . ParseStatement ( )
2015-07-22 19:49:17 +00:00
if err != nil {
t . Fatalf ( "failed to parse string: %v\norig: %v\ngot: %v" , err , tt . stmt , stmt . String ( ) )
}
2015-11-22 20:38:48 +00:00
if ! reflect . DeepEqual ( stmt , stmtCopy ) {
t . Fatalf ( "statement changed after stringifying and re-parsing:\noriginal : %v\nre-parsed: %v\n" , tt . stmt , stmtCopy . String ( ) )
}
2015-07-22 19:49:17 +00:00
}
}
2015-01-28 00:20:34 +00:00
// Ensure an expression can be reduced.
func TestEval ( t * testing . T ) {
for i , tt := range [ ] struct {
in string
out interface { }
data map [ string ] interface { }
} {
// Number literals.
2016-02-19 04:06:41 +00:00
{ in : ` 1 + 2 ` , out : int64 ( 3 ) } ,
2015-01-28 00:20:34 +00:00
{ in : ` (foo*2) + ( (4/2) + (3 * 5) - 0.5 ) ` , out : float64 ( 26.5 ) , data : map [ string ] interface { } { "foo" : float64 ( 5 ) } } ,
{ in : ` foo / 2 ` , out : float64 ( 2 ) , data : map [ string ] interface { } { "foo" : float64 ( 4 ) } } ,
{ in : ` 4 = 4 ` , out : true } ,
{ in : ` 4 <> 4 ` , out : false } ,
{ in : ` 6 > 4 ` , out : true } ,
{ in : ` 4 >= 4 ` , out : true } ,
{ in : ` 4 < 6 ` , out : true } ,
{ in : ` 4 <= 4 ` , out : true } ,
{ in : ` 4 AND 5 ` , out : nil } ,
2016-02-19 04:06:41 +00:00
{ in : ` 0 = 'test' ` , out : false } ,
{ in : ` 1.0 = 1 ` , out : true } ,
{ in : ` 1.2 = 1 ` , out : false } ,
2015-01-28 00:20:34 +00:00
// Boolean literals.
{ in : ` true AND false ` , out : false } ,
{ in : ` true OR false ` , out : true } ,
2016-03-15 01:30:37 +00:00
{ in : ` false = 4 ` , out : false } ,
2015-01-28 00:20:34 +00:00
// String literals.
{ in : ` 'foo' = 'bar' ` , out : false } ,
{ in : ` 'foo' = 'foo' ` , out : true } ,
2016-03-15 01:30:37 +00:00
{ in : ` '' = 4 ` , out : false } ,
2015-01-28 00:20:34 +00:00
2016-03-09 17:56:16 +00:00
// Regex literals.
{ in : ` 'foo' =~ /f.*/ ` , out : true } ,
{ in : ` 'foo' =~ /b.*/ ` , out : false } ,
{ in : ` 'foo' !~ /f.*/ ` , out : false } ,
{ in : ` 'foo' !~ /b.*/ ` , out : true } ,
2015-01-28 00:20:34 +00:00
// Variable references.
{ in : ` foo ` , out : "bar" , data : map [ string ] interface { } { "foo" : "bar" } } ,
{ in : ` foo = 'bar' ` , out : true , data : map [ string ] interface { } { "foo" : "bar" } } ,
{ in : ` foo = 'bar' ` , out : nil , data : map [ string ] interface { } { "foo" : nil } } ,
{ in : ` foo <> 'bar' ` , out : true , data : map [ string ] interface { } { "foo" : "xxx" } } ,
2016-03-09 17:56:16 +00:00
{ in : ` foo =~ /b.*/ ` , out : true , data : map [ string ] interface { } { "foo" : "bar" } } ,
{ in : ` foo !~ /b.*/ ` , out : false , data : map [ string ] interface { } { "foo" : "bar" } } ,
2015-01-28 00:20:34 +00:00
} {
// Evaluate expression.
out := influxql . Eval ( MustParseExpr ( tt . in ) , tt . data )
// Compare with expected output.
if ! reflect . DeepEqual ( tt . out , out ) {
t . Errorf ( "%d. %s: unexpected output:\n\nexp=%#v\n\ngot=%#v\n\n" , i , tt . in , tt . out , out )
continue
}
}
}
2015-01-23 09:44:56 +00:00
// Ensure an expression can be reduced.
func TestReduce ( t * testing . T ) {
now := mustParseTime ( "2000-01-01T00:00:00Z" )
for i , tt := range [ ] struct {
in string
out string
data Valuer
} {
// Number literals.
2016-02-19 04:06:41 +00:00
{ in : ` 1 + 2 ` , out : ` 3 ` } ,
{ in : ` (foo*2) + ( (4/2) + (3 * 5) - 0.5 ) ` , out : ` (foo * 2) + 16.500 ` } ,
{ in : ` foo(bar(2 + 3), 4) ` , out : ` foo(bar(5), 4) ` } ,
2015-01-23 09:44:56 +00:00
{ in : ` 4 / 0 ` , out : ` 0.000 ` } ,
2016-02-19 04:06:41 +00:00
{ in : ` 1 / 2 ` , out : ` 0.500 ` } ,
2015-01-23 09:44:56 +00:00
{ in : ` 4 = 4 ` , out : ` true ` } ,
{ in : ` 4 <> 4 ` , out : ` false ` } ,
{ in : ` 6 > 4 ` , out : ` true ` } ,
{ in : ` 4 >= 4 ` , out : ` true ` } ,
{ in : ` 4 < 6 ` , out : ` true ` } ,
{ in : ` 4 <= 4 ` , out : ` true ` } ,
2016-02-19 04:06:41 +00:00
{ in : ` 4 AND 5 ` , out : ` 4 AND 5 ` } ,
2015-01-23 09:44:56 +00:00
// Boolean literals.
{ in : ` true AND false ` , out : ` false ` } ,
{ in : ` true OR false ` , out : ` true ` } ,
{ in : ` true OR (foo = bar AND 1 > 2) ` , out : ` true ` } ,
{ in : ` (foo = bar AND 1 > 2) OR true ` , out : ` true ` } ,
{ in : ` false OR (foo = bar AND 1 > 2) ` , out : ` false ` } ,
{ in : ` (foo = bar AND 1 > 2) OR false ` , out : ` false ` } ,
{ in : ` true = false ` , out : ` false ` } ,
{ in : ` true <> false ` , out : ` true ` } ,
{ in : ` true + false ` , out : ` true + false ` } ,
2016-09-09 18:34:54 +00:00
// Time literals with now().
2015-07-29 19:33:03 +00:00
{ in : ` now() + 2h ` , out : ` '2000-01-01T02:00:00Z' ` , data : map [ string ] interface { } { "now()" : now } } ,
{ in : ` now() / 2h ` , out : ` '2000-01-01T00:00:00Z' / 2h ` , data : map [ string ] interface { } { "now()" : now } } ,
{ in : ` 4µ + now() ` , out : ` '2000-01-01T00:00:00.000004Z' ` , data : map [ string ] interface { } { "now()" : now } } ,
2016-09-09 18:34:54 +00:00
{ in : ` now() + 2000000000 ` , out : ` '2000-01-01T00:00:02Z' ` , data : map [ string ] interface { } { "now()" : now } } ,
{ in : ` 2000000000 + now() ` , out : ` '2000-01-01T00:00:02Z' ` , data : map [ string ] interface { } { "now()" : now } } ,
{ in : ` now() - 2000000000 ` , out : ` '1999-12-31T23:59:58Z' ` , data : map [ string ] interface { } { "now()" : now } } ,
2015-01-23 09:44:56 +00:00
{ in : ` now() = now() ` , out : ` true ` , data : map [ string ] interface { } { "now()" : now } } ,
{ in : ` now() <> now() ` , out : ` false ` , data : map [ string ] interface { } { "now()" : now } } ,
{ in : ` now() < now() + 1h ` , out : ` true ` , data : map [ string ] interface { } { "now()" : now } } ,
{ in : ` now() <= now() + 1h ` , out : ` true ` , data : map [ string ] interface { } { "now()" : now } } ,
{ in : ` now() >= now() - 1h ` , out : ` true ` , data : map [ string ] interface { } { "now()" : now } } ,
{ in : ` now() > now() - 1h ` , out : ` true ` , data : map [ string ] interface { } { "now()" : now } } ,
{ in : ` now() - (now() - 60s) ` , out : ` 1m ` , data : map [ string ] interface { } { "now()" : now } } ,
2015-07-29 19:33:03 +00:00
{ in : ` now() AND now() ` , out : ` '2000-01-01T00:00:00Z' AND '2000-01-01T00:00:00Z' ` , data : map [ string ] interface { } { "now()" : now } } ,
2015-01-23 09:44:56 +00:00
{ in : ` now() ` , out : ` now() ` } ,
2016-04-15 19:06:07 +00:00
{ in : ` 946684800000000000 + 2h ` , out : ` '2000-01-01T02:00:00Z' ` } ,
2015-01-23 09:44:56 +00:00
2016-09-09 18:34:54 +00:00
// Time literals.
{ in : ` '2000-01-01T00:00:00Z' + 2h ` , out : ` '2000-01-01T02:00:00Z' ` } ,
{ in : ` '2000-01-01T00:00:00Z' / 2h ` , out : ` '2000-01-01T00:00:00Z' / 2h ` } ,
{ in : ` 4µ + '2000-01-01T00:00:00Z' ` , out : ` '2000-01-01T00:00:00.000004Z' ` } ,
{ in : ` '2000-01-01T00:00:00Z' + 2000000000 ` , out : ` '2000-01-01T00:00:02Z' ` } ,
{ in : ` 2000000000 + '2000-01-01T00:00:00Z' ` , out : ` '2000-01-01T00:00:02Z' ` } ,
{ in : ` '2000-01-01T00:00:00Z' - 2000000000 ` , out : ` '1999-12-31T23:59:58Z' ` } ,
{ in : ` '2000-01-01T00:00:00Z' = '2000-01-01T00:00:00Z' ` , out : ` true ` } ,
{ in : ` '2000-01-01T00:00:00.000000000Z' = '2000-01-01T00:00:00Z' ` , out : ` true ` } ,
{ in : ` '2000-01-01T00:00:00Z' <> '2000-01-01T00:00:00Z' ` , out : ` false ` } ,
{ in : ` '2000-01-01T00:00:00.000000000Z' <> '2000-01-01T00:00:00Z' ` , out : ` false ` } ,
{ in : ` '2000-01-01T00:00:00Z' < '2000-01-01T00:00:00Z' + 1h ` , out : ` true ` } ,
{ in : ` '2000-01-01T00:00:00.000000000Z' < '2000-01-01T00:00:00Z' + 1h ` , out : ` true ` } ,
{ in : ` '2000-01-01T00:00:00Z' <= '2000-01-01T00:00:00Z' + 1h ` , out : ` true ` } ,
{ in : ` '2000-01-01T00:00:00.000000000Z' <= '2000-01-01T00:00:00Z' + 1h ` , out : ` true ` } ,
{ in : ` '2000-01-01T00:00:00Z' > '2000-01-01T00:00:00Z' - 1h ` , out : ` true ` } ,
{ in : ` '2000-01-01T00:00:00.000000000Z' > '2000-01-01T00:00:00Z' - 1h ` , out : ` true ` } ,
{ in : ` '2000-01-01T00:00:00Z' >= '2000-01-01T00:00:00Z' - 1h ` , out : ` true ` } ,
{ in : ` '2000-01-01T00:00:00.000000000Z' >= '2000-01-01T00:00:00Z' - 1h ` , out : ` true ` } ,
{ in : ` '2000-01-01T00:00:00Z' - ('2000-01-01T00:00:00Z' - 60s) ` , out : ` 1m ` } ,
{ in : ` '2000-01-01T00:00:00Z' AND '2000-01-01T00:00:00Z' ` , out : ` '2000-01-01T00:00:00Z' AND '2000-01-01T00:00:00Z' ` } ,
2015-01-23 09:44:56 +00:00
// Duration literals.
{ in : ` 10m + 1h - 60s ` , out : ` 69m ` } ,
{ in : ` (10m / 2) * 5 ` , out : ` 25m ` } ,
{ in : ` 60s = 1m ` , out : ` true ` } ,
{ in : ` 60s <> 1m ` , out : ` false ` } ,
{ in : ` 60s < 1h ` , out : ` true ` } ,
{ in : ` 60s <= 1h ` , out : ` true ` } ,
{ in : ` 60s > 12s ` , out : ` true ` } ,
{ in : ` 60s >= 1m ` , out : ` true ` } ,
{ in : ` 60s AND 1m ` , out : ` 1m AND 1m ` } ,
{ in : ` 60m / 0 ` , out : ` 0s ` } ,
2016-02-19 04:06:41 +00:00
{ in : ` 60m + 50 ` , out : ` 1h + 50 ` } ,
2015-01-23 09:44:56 +00:00
// String literals.
{ in : ` 'foo' + 'bar' ` , out : ` 'foobar' ` } ,
// Variable references.
{ in : ` foo ` , out : ` 'bar' ` , data : map [ string ] interface { } { "foo" : "bar" } } ,
{ in : ` foo = 'bar' ` , out : ` true ` , data : map [ string ] interface { } { "foo" : "bar" } } ,
{ in : ` foo = 'bar' ` , out : ` false ` , data : map [ string ] interface { } { "foo" : nil } } ,
{ in : ` foo <> 'bar' ` , out : ` false ` , data : map [ string ] interface { } { "foo" : nil } } ,
} {
// Fold expression.
expr := influxql . Reduce ( MustParseExpr ( tt . in ) , tt . data )
// Compare with expected output.
if out := expr . String ( ) ; tt . out != out {
t . Errorf ( "%d. %s: unexpected expr:\n\nexp=%s\n\ngot=%s\n\n" , i , tt . in , tt . out , out )
continue
}
}
}
2015-12-04 19:05:02 +00:00
func Test_fieldsNames ( t * testing . T ) {
for _ , test := range [ ] struct {
in [ ] string
out [ ] string
alias [ ] string
} {
{ //case: binary expr(valRef)
in : [ ] string { "value+value" } ,
out : [ ] string { "value" , "value" } ,
2016-02-18 00:24:13 +00:00
alias : [ ] string { "value_value" } ,
2015-12-04 19:05:02 +00:00
} ,
{ //case: binary expr + valRef
in : [ ] string { "value+value" , "temperature" } ,
out : [ ] string { "value" , "value" , "temperature" } ,
2016-02-18 00:24:13 +00:00
alias : [ ] string { "value_value" , "temperature" } ,
2015-12-04 19:05:02 +00:00
} ,
{ //case: aggregate expr
in : [ ] string { "mean(value)" } ,
out : [ ] string { "mean" } ,
alias : [ ] string { "mean" } ,
} ,
{ //case: binary expr(aggregate expr)
in : [ ] string { "mean(value) + max(value)" } ,
out : [ ] string { "value" , "value" } ,
2016-02-18 00:24:13 +00:00
alias : [ ] string { "mean_max" } ,
2015-12-04 19:05:02 +00:00
} ,
{ //case: binary expr(aggregate expr) + valRef
in : [ ] string { "mean(value) + max(value)" , "temperature" } ,
out : [ ] string { "value" , "value" , "temperature" } ,
2016-02-18 00:24:13 +00:00
alias : [ ] string { "mean_max" , "temperature" } ,
2015-12-04 19:05:02 +00:00
} ,
{ //case: mixed aggregate and varRef
in : [ ] string { "mean(value) + temperature" } ,
out : [ ] string { "value" , "temperature" } ,
2016-02-18 00:24:13 +00:00
alias : [ ] string { "mean_temperature" } ,
2015-12-04 19:05:02 +00:00
} ,
2015-12-09 07:27:33 +00:00
{ //case: ParenExpr(varRef)
in : [ ] string { "(value)" } ,
out : [ ] string { "value" } ,
2016-02-18 00:24:13 +00:00
alias : [ ] string { "value" } ,
2015-12-09 07:27:33 +00:00
} ,
{ //case: ParenExpr(varRef + varRef)
in : [ ] string { "(value + value)" } ,
out : [ ] string { "value" , "value" } ,
2016-02-18 00:24:13 +00:00
alias : [ ] string { "value_value" } ,
2015-12-09 07:27:33 +00:00
} ,
{ //case: ParenExpr(aggregate)
in : [ ] string { "(mean(value))" } ,
out : [ ] string { "value" } ,
2016-02-18 00:24:13 +00:00
alias : [ ] string { "mean" } ,
2015-12-09 07:27:33 +00:00
} ,
{ //case: ParenExpr(aggregate + aggregate)
in : [ ] string { "(mean(value) + max(value))" } ,
out : [ ] string { "value" , "value" } ,
2016-02-18 00:24:13 +00:00
alias : [ ] string { "mean_max" } ,
2015-12-09 07:27:33 +00:00
} ,
2015-12-04 19:05:02 +00:00
} {
fields := influxql . Fields { }
for _ , s := range test . in {
expr := MustParseExpr ( s )
fields = append ( fields , & influxql . Field { Expr : expr } )
}
got := fields . Names ( )
if ! reflect . DeepEqual ( got , test . out ) {
2016-02-18 00:24:13 +00:00
t . Errorf ( "get fields name:\nexp=%v\ngot=%v\n" , test . out , got )
2015-12-04 19:05:02 +00:00
}
alias := fields . AliasNames ( )
if ! reflect . DeepEqual ( alias , test . alias ) {
2016-02-18 00:24:13 +00:00
t . Errorf ( "get fields alias name:\nexp=%v\ngot=%v\n" , test . alias , alias )
2015-12-04 19:05:02 +00:00
}
}
}
2016-02-18 19:15:08 +00:00
func TestSelect_ColumnNames ( t * testing . T ) {
for i , tt := range [ ] struct {
stmt * influxql . SelectStatement
columns [ ] string
} {
{
stmt : & influxql . SelectStatement {
Fields : influxql . Fields ( [ ] * influxql . Field {
{ Expr : & influxql . VarRef { Val : "value" } } ,
} ) ,
} ,
columns : [ ] string { "time" , "value" } ,
} ,
{
stmt : & influxql . SelectStatement {
Fields : influxql . Fields ( [ ] * influxql . Field {
{ Expr : & influxql . VarRef { Val : "value" } } ,
{ Expr : & influxql . VarRef { Val : "value" } } ,
{ Expr : & influxql . VarRef { Val : "value_1" } } ,
} ) ,
} ,
columns : [ ] string { "time" , "value" , "value_1" , "value_1_1" } ,
} ,
{
stmt : & influxql . SelectStatement {
Fields : influxql . Fields ( [ ] * influxql . Field {
{ Expr : & influxql . VarRef { Val : "value" } } ,
{ Expr : & influxql . VarRef { Val : "value_1" } } ,
{ Expr : & influxql . VarRef { Val : "value" } } ,
} ) ,
} ,
columns : [ ] string { "time" , "value" , "value_1" , "value_2" } ,
} ,
{
stmt : & influxql . SelectStatement {
Fields : influxql . Fields ( [ ] * influxql . Field {
{ Expr : & influxql . VarRef { Val : "value" } } ,
{ Expr : & influxql . VarRef { Val : "total" } , Alias : "value" } ,
{ Expr : & influxql . VarRef { Val : "value" } } ,
} ) ,
} ,
columns : [ ] string { "time" , "value_1" , "value" , "value_2" } ,
} ,
2016-04-13 22:52:22 +00:00
{
stmt : & influxql . SelectStatement {
Fields : influxql . Fields ( [ ] * influxql . Field {
{ Expr : & influxql . VarRef { Val : "value" } } ,
} ) ,
TimeAlias : "timestamp" ,
} ,
columns : [ ] string { "timestamp" , "value" } ,
} ,
2016-02-18 19:15:08 +00:00
} {
columns := tt . stmt . ColumnNames ( )
if ! reflect . DeepEqual ( columns , tt . columns ) {
t . Errorf ( "%d. expected %s, got %s" , i , tt . columns , columns )
}
}
}
2016-05-23 19:57:36 +00:00
func TestSelect_Privileges ( t * testing . T ) {
stmt := & influxql . SelectStatement {
Target : & influxql . Target {
Measurement : & influxql . Measurement { Database : "db2" } ,
} ,
Sources : [ ] influxql . Source {
& influxql . Measurement { Database : "db0" } ,
& influxql . Measurement { Database : "db1" } ,
} ,
}
exp := influxql . ExecutionPrivileges {
influxql . ExecutionPrivilege { Name : "db0" , Privilege : influxql . ReadPrivilege } ,
influxql . ExecutionPrivilege { Name : "db1" , Privilege : influxql . ReadPrivilege } ,
influxql . ExecutionPrivilege { Name : "db2" , Privilege : influxql . WritePrivilege } ,
}
got , err := stmt . RequiredPrivileges ( )
if err != nil {
t . Fatal ( err )
}
if ! reflect . DeepEqual ( exp , got ) {
t . Errorf ( "exp: %v, got: %v" , exp , got )
}
}
2016-02-09 16:25:16 +00:00
func TestSources_Names ( t * testing . T ) {
2016-02-09 20:18:31 +00:00
sources := influxql . Sources ( [ ] influxql . Source {
2016-02-09 16:25:16 +00:00
& influxql . Measurement {
Name : "cpu" ,
} ,
& influxql . Measurement {
Name : "mem" ,
} ,
2016-02-09 20:18:31 +00:00
} )
2016-02-09 16:25:16 +00:00
names := sources . Names ( )
if names [ 0 ] != "cpu" {
t . Errorf ( "expected cpu, got %s" , names [ 0 ] )
}
if names [ 1 ] != "mem" {
t . Errorf ( "expected mem, got %s" , names [ 1 ] )
}
}
func TestSources_HasSystemSource ( t * testing . T ) {
2016-02-09 20:18:31 +00:00
sources := influxql . Sources ( [ ] influxql . Source {
2016-02-09 16:25:16 +00:00
& influxql . Measurement {
Name : "_measurements" ,
} ,
2016-02-09 20:18:31 +00:00
} )
2016-02-09 16:25:16 +00:00
ok := sources . HasSystemSource ( )
if ! ok {
t . Errorf ( "expected to find a system source, found none" )
}
2016-02-09 20:18:31 +00:00
sources = influxql . Sources ( [ ] influxql . Source {
2016-02-09 16:25:16 +00:00
& influxql . Measurement {
Name : "cpu" ,
} ,
2016-02-09 20:18:31 +00:00
} )
2016-02-09 16:25:16 +00:00
ok = sources . HasSystemSource ( )
if ok {
t . Errorf ( "expected to find no system source, found one" )
}
}
2016-10-17 23:20:53 +00:00
// Parse statements that might appear valid but should return an error.
// If allowed to execute, at least some of these statements would result in a panic.
func TestParse_Errors ( t * testing . T ) {
for _ , tt := range [ ] struct {
tmpl string
good string
bad string
} {
// Second argument to derivative must be duration
{ tmpl : ` SELECT derivative(f, %s) FROM m ` , good : "1h" , bad : "true" } ,
} {
good := fmt . Sprintf ( tt . tmpl , tt . good )
if _ , err := influxql . ParseStatement ( good ) ; err != nil {
t . Fatalf ( "statement %q should have parsed correctly but returned error: %s" , good , err )
}
bad := fmt . Sprintf ( tt . tmpl , tt . bad )
if _ , err := influxql . ParseStatement ( bad ) ; err == nil {
t . Fatalf ( "statement %q should have resulted in a parse error but did not" , bad )
}
}
}
2015-01-23 09:44:56 +00:00
// Valuer represents a simple wrapper around a map to implement the influxql.Valuer interface.
type Valuer map [ string ] interface { }
// Value returns the value and existence of a key.
func ( o Valuer ) Value ( key string ) ( v interface { } , ok bool ) {
v , ok = o [ key ]
return
}
2015-03-06 19:23:58 +00:00
2016-03-28 17:26:13 +00:00
// MustTimeRange will parse a time range. Panic on error.
func MustTimeRange ( expr influxql . Expr ) ( min , max time . Time ) {
min , max , err := influxql . TimeRange ( expr )
if err != nil {
panic ( err )
}
return min , max
}
2015-03-06 19:23:58 +00:00
// mustParseTime parses an IS0-8601 string. Panic on error.
func mustParseTime ( s string ) time . Time {
t , err := time . Parse ( time . RFC3339 , s )
if err != nil {
panic ( err . Error ( ) )
}
return t
}