diff --git a/CHANGELOG.md b/CHANGELOG.md index 60b668f4a45..03351061849 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ - [#8601](https://github.com/influxdata/influxdb/pull/8601): Fixed time boundaries for continuous queries with time zones. - [#8097](https://github.com/influxdata/influxdb/pull/8097): Return query parsing errors in CSV formats. - [#8607](https://github.com/influxdata/influxdb/issues/8607): Fix time zone shifts when the shift happens on a time zone boundary. +- [#8639](https://github.com/influxdata/influxdb/issues/8639): Parse time literals using the time zone in the select statement. ## v1.3.1 [unreleased] diff --git a/coordinator/statement_executor.go b/coordinator/statement_executor.go index 7300f1a8492..21ece234740 100644 --- a/coordinator/statement_executor.go +++ b/coordinator/statement_executor.go @@ -534,11 +534,11 @@ func (e *StatementExecutor) createIterators(stmt *influxql.SelectStatement, ctx } // Replace instances of "now()" with the current time, and check the resultant times. - nowValuer := influxql.NowValuer{Now: now} + nowValuer := influxql.NowValuer{Now: now, Location: stmt.Location} stmt = stmt.Reduce(&nowValuer) var err error - opt.MinTime, opt.MaxTime, err = influxql.TimeRange(stmt.Condition) + opt.MinTime, opt.MaxTime, err = influxql.TimeRange(stmt.Condition, stmt.Location) if err != nil { return nil, stmt, err } diff --git a/influxql/ast.go b/influxql/ast.go index 8ad188de58d..1cd25237fd7 100644 --- a/influxql/ast.go +++ b/influxql/ast.go @@ -1482,7 +1482,7 @@ func (s *SelectStatement) RewriteTimeCondition(now time.Time) error { if err != nil { return err } else if interval > 0 && s.Condition != nil { - _, tmax, err := TimeRange(s.Condition) + _, tmax, err := TimeRange(s.Condition, s.Location) if err != nil { return err } @@ -3778,19 +3778,23 @@ func (l *StringLiteral) IsTimeLiteral() bool { } // ToTimeLiteral returns a time literal if this string can be converted to a time literal. -func (l *StringLiteral) ToTimeLiteral() (*TimeLiteral, error) { +func (l *StringLiteral) ToTimeLiteral(loc *time.Location) (*TimeLiteral, error) { + if loc == nil { + loc = time.UTC + } + if isDateTimeString(l.Val) { - t, err := time.Parse(DateTimeFormat, l.Val) + t, err := time.ParseInLocation(DateTimeFormat, l.Val, loc) if err != nil { // try to parse it as an RFCNano time - t, err = time.Parse(time.RFC3339Nano, l.Val) + t, err = time.ParseInLocation(time.RFC3339Nano, l.Val, loc) if err != nil { return nil, ErrInvalidTime } } return &TimeLiteral{Val: t}, nil } else if isDateString(l.Val) { - t, err := time.Parse(DateFormat, l.Val) + t, err := time.ParseInLocation(DateFormat, l.Val, loc) if err != nil { return nil, ErrInvalidTime } @@ -4040,7 +4044,7 @@ func OnlyTimeExpr(expr Expr) bool { // TimeRange returns the minimum and maximum times specified by an expression. // It returns zero times if there is no bound. -func TimeRange(expr Expr) (min, max time.Time, err error) { +func TimeRange(expr Expr, loc *time.Location) (min, max time.Time, err error) { WalkFunc(expr, func(n Node) { if err != nil { return @@ -4052,11 +4056,11 @@ func TimeRange(expr Expr) (min, max time.Time, err error) { // Otherwise check for for the right-hand side and flip the operator. op := n.Op var value time.Time - value, err = timeExprValue(n.LHS, n.RHS) + value, err = timeExprValue(n.LHS, n.RHS, loc) if err != nil { return } else if value.IsZero() { - if value, err = timeExprValue(n.RHS, n.LHS); value.IsZero() || err != nil { + if value, err = timeExprValue(n.RHS, n.LHS, loc); value.IsZero() || err != nil { return } else if op == LT { op = GT @@ -4105,7 +4109,7 @@ func TimeRange(expr Expr) (min, max time.Time, err error) { // an expression. If there is no lower bound, the minimum time is returned // for minimum. If there is no higher bound, the maximum time is returned. func TimeRangeAsEpochNano(expr Expr) (min, max int64, err error) { - tmin, tmax, err := TimeRange(expr) + tmin, tmax, err := TimeRange(expr, nil) if err != nil { return 0, 0, err } @@ -4125,12 +4129,12 @@ func TimeRangeAsEpochNano(expr Expr) (min, max int64, err error) { // timeExprValue returns the time literal value of a "time == " expression. // Returns zero time if the expression is not a time expression. -func timeExprValue(ref Expr, lit Expr) (t time.Time, err error) { +func timeExprValue(ref Expr, lit Expr, loc *time.Location) (t time.Time, err error) { if ref, ok := ref.(*VarRef); ok && strings.ToLower(ref.Val) == "time" { // If literal looks like a date time then parse it as a time literal. if strlit, ok := lit.(*StringLiteral); ok { if strlit.IsTimeLiteral() { - t, err := strlit.ToTimeLiteral() + t, err := strlit.ToTimeLiteral(loc) if err != nil { return time.Time{}, err } @@ -4797,6 +4801,11 @@ func reduceBinaryExpr(expr *BinaryExpr, valuer Valuer) Expr { lhs := reduce(expr.LHS, valuer) rhs := reduce(expr.RHS, valuer) + loc := time.UTC + if v, ok := valuer.(ZoneValuer); ok { + loc = v.Zone() + } + // Do not evaluate if one side is nil. if lhs == nil || rhs == nil { return &BinaryExpr{LHS: lhs, RHS: rhs, Op: expr.Op} @@ -4827,17 +4836,17 @@ func reduceBinaryExpr(expr *BinaryExpr, valuer Valuer) Expr { case *BooleanLiteral: return reduceBinaryExprBooleanLHS(op, lhs, rhs) case *DurationLiteral: - return reduceBinaryExprDurationLHS(op, lhs, rhs) + return reduceBinaryExprDurationLHS(op, lhs, rhs, loc) case *IntegerLiteral: - return reduceBinaryExprIntegerLHS(op, lhs, rhs) + return reduceBinaryExprIntegerLHS(op, lhs, rhs, loc) case *nilLiteral: return reduceBinaryExprNilLHS(op, lhs, rhs) case *NumberLiteral: return reduceBinaryExprNumberLHS(op, lhs, rhs) case *StringLiteral: - return reduceBinaryExprStringLHS(op, lhs, rhs) + return reduceBinaryExprStringLHS(op, lhs, rhs, loc) case *TimeLiteral: - return reduceBinaryExprTimeLHS(op, lhs, rhs) + return reduceBinaryExprTimeLHS(op, lhs, rhs, loc) default: return &BinaryExpr{Op: op, LHS: lhs, RHS: rhs} } @@ -4868,7 +4877,7 @@ func reduceBinaryExprBooleanLHS(op Token, lhs *BooleanLiteral, rhs Expr) Expr { return &BinaryExpr{Op: op, LHS: lhs, RHS: rhs} } -func reduceBinaryExprDurationLHS(op Token, lhs *DurationLiteral, rhs Expr) Expr { +func reduceBinaryExprDurationLHS(op Token, lhs *DurationLiteral, rhs Expr, loc *time.Location) Expr { switch rhs := rhs.(type) { case *DurationLiteral: switch op { @@ -4915,11 +4924,11 @@ func reduceBinaryExprDurationLHS(op Token, lhs *DurationLiteral, rhs Expr) Expr return &TimeLiteral{Val: rhs.Val.Add(lhs.Val)} } case *StringLiteral: - t, err := rhs.ToTimeLiteral() + t, err := rhs.ToTimeLiteral(loc) if err != nil { break } - expr := reduceBinaryExprDurationLHS(op, lhs, t) + expr := reduceBinaryExprDurationLHS(op, lhs, t, loc) // If the returned expression is still a binary expr, that means // we couldn't reduce it so this wasn't used in a time literal context. @@ -4932,7 +4941,7 @@ func reduceBinaryExprDurationLHS(op Token, lhs *DurationLiteral, rhs Expr) Expr return &BinaryExpr{Op: op, LHS: lhs, RHS: rhs} } -func reduceBinaryExprIntegerLHS(op Token, lhs *IntegerLiteral, rhs Expr) Expr { +func reduceBinaryExprIntegerLHS(op Token, lhs *IntegerLiteral, rhs Expr, loc *time.Location) Expr { switch rhs := rhs.(type) { case *NumberLiteral: return reduceBinaryExprNumberLHS(op, &NumberLiteral{Val: float64(lhs.Val)}, rhs) @@ -4983,17 +4992,17 @@ func reduceBinaryExprIntegerLHS(op Token, lhs *IntegerLiteral, rhs Expr) Expr { } case *TimeLiteral: d := &DurationLiteral{Val: time.Duration(lhs.Val)} - expr := reduceBinaryExprDurationLHS(op, d, rhs) + expr := reduceBinaryExprDurationLHS(op, d, rhs, loc) if _, ok := expr.(*BinaryExpr); !ok { return expr } case *StringLiteral: - t, err := rhs.ToTimeLiteral() + t, err := rhs.ToTimeLiteral(loc) if err != nil { break } d := &DurationLiteral{Val: time.Duration(lhs.Val)} - expr := reduceBinaryExprDurationLHS(op, d, t) + expr := reduceBinaryExprDurationLHS(op, d, t, loc) if _, ok := expr.(*BinaryExpr); !ok { return expr } @@ -5075,7 +5084,7 @@ func reduceBinaryExprNumberLHS(op Token, lhs *NumberLiteral, rhs Expr) Expr { return &BinaryExpr{Op: op, LHS: lhs, RHS: rhs} } -func reduceBinaryExprStringLHS(op Token, lhs *StringLiteral, rhs Expr) Expr { +func reduceBinaryExprStringLHS(op Token, lhs *StringLiteral, rhs Expr, loc *time.Location) Expr { switch rhs := rhs.(type) { case *StringLiteral: switch op { @@ -5086,17 +5095,17 @@ func reduceBinaryExprStringLHS(op Token, lhs *StringLiteral, rhs Expr) Expr { // could be a different result if they use different formats // for the same time. if lhs.IsTimeLiteral() && rhs.IsTimeLiteral() { - tlhs, err := lhs.ToTimeLiteral() + tlhs, err := lhs.ToTimeLiteral(loc) if err != nil { return expr } - trhs, err := rhs.ToTimeLiteral() + trhs, err := rhs.ToTimeLiteral(loc) if err != nil { return expr } - t := reduceBinaryExprTimeLHS(op, tlhs, trhs) + t := reduceBinaryExprTimeLHS(op, tlhs, trhs, loc) if _, ok := t.(*BinaryExpr); !ok { expr = t } @@ -5109,17 +5118,17 @@ func reduceBinaryExprStringLHS(op Token, lhs *StringLiteral, rhs Expr) Expr { // could be a different result if they use different formats // for the same time. if lhs.IsTimeLiteral() && rhs.IsTimeLiteral() { - tlhs, err := lhs.ToTimeLiteral() + tlhs, err := lhs.ToTimeLiteral(loc) if err != nil { return expr } - trhs, err := rhs.ToTimeLiteral() + trhs, err := rhs.ToTimeLiteral(loc) if err != nil { return expr } - t := reduceBinaryExprTimeLHS(op, tlhs, trhs) + t := reduceBinaryExprTimeLHS(op, tlhs, trhs, loc) if _, ok := t.(*BinaryExpr); !ok { expr = t } @@ -5129,11 +5138,11 @@ func reduceBinaryExprStringLHS(op Token, lhs *StringLiteral, rhs Expr) Expr { return &StringLiteral{Val: lhs.Val + rhs.Val} default: // Attempt to convert the string literal to a time literal. - t, err := lhs.ToTimeLiteral() + t, err := lhs.ToTimeLiteral(loc) if err != nil { break } - expr := reduceBinaryExprTimeLHS(op, t, rhs) + expr := reduceBinaryExprTimeLHS(op, t, rhs, loc) // If the returned expression is still a binary expr, that means // we couldn't reduce it so this wasn't used in a time literal context. @@ -5143,11 +5152,11 @@ func reduceBinaryExprStringLHS(op Token, lhs *StringLiteral, rhs Expr) Expr { } case *DurationLiteral: // Attempt to convert the string literal to a time literal. - t, err := lhs.ToTimeLiteral() + t, err := lhs.ToTimeLiteral(loc) if err != nil { break } - expr := reduceBinaryExprTimeLHS(op, t, rhs) + expr := reduceBinaryExprTimeLHS(op, t, rhs, loc) // If the returned expression is still a binary expr, that means // we couldn't reduce it so this wasn't used in a time literal context. @@ -5156,11 +5165,11 @@ func reduceBinaryExprStringLHS(op Token, lhs *StringLiteral, rhs Expr) Expr { } case *TimeLiteral: // Attempt to convert the string literal to a time literal. - t, err := lhs.ToTimeLiteral() + t, err := lhs.ToTimeLiteral(loc) if err != nil { break } - expr := reduceBinaryExprTimeLHS(op, t, rhs) + expr := reduceBinaryExprTimeLHS(op, t, rhs, loc) // If the returned expression is still a binary expr, that means // we couldn't reduce it so this wasn't used in a time literal context. @@ -5169,11 +5178,11 @@ func reduceBinaryExprStringLHS(op Token, lhs *StringLiteral, rhs Expr) Expr { } case *IntegerLiteral: // Attempt to convert the string literal to a time literal. - t, err := lhs.ToTimeLiteral() + t, err := lhs.ToTimeLiteral(loc) if err != nil { break } - expr := reduceBinaryExprTimeLHS(op, t, rhs) + expr := reduceBinaryExprTimeLHS(op, t, rhs, loc) // If the returned expression is still a binary expr, that means // we couldn't reduce it so this wasn't used in a time literal context. @@ -5189,7 +5198,7 @@ func reduceBinaryExprStringLHS(op Token, lhs *StringLiteral, rhs Expr) Expr { return &BinaryExpr{Op: op, LHS: lhs, RHS: rhs} } -func reduceBinaryExprTimeLHS(op Token, lhs *TimeLiteral, rhs Expr) Expr { +func reduceBinaryExprTimeLHS(op Token, lhs *TimeLiteral, rhs Expr, loc *time.Location) Expr { switch rhs := rhs.(type) { case *DurationLiteral: switch op { @@ -5200,7 +5209,7 @@ func reduceBinaryExprTimeLHS(op Token, lhs *TimeLiteral, rhs Expr) Expr { } case *IntegerLiteral: d := &DurationLiteral{Val: time.Duration(rhs.Val)} - expr := reduceBinaryExprTimeLHS(op, lhs, d) + expr := reduceBinaryExprTimeLHS(op, lhs, d, loc) if _, ok := expr.(*BinaryExpr); !ok { return expr } @@ -5222,11 +5231,11 @@ func reduceBinaryExprTimeLHS(op Token, lhs *TimeLiteral, rhs Expr) Expr { return &BooleanLiteral{Val: lhs.Val.Before(rhs.Val) || lhs.Val.Equal(rhs.Val)} } case *StringLiteral: - t, err := rhs.ToTimeLiteral() + t, err := rhs.ToTimeLiteral(loc) if err != nil { break } - expr := reduceBinaryExprTimeLHS(op, lhs, t) + expr := reduceBinaryExprTimeLHS(op, lhs, t, loc) // If the returned expression is still a binary expr, that means // we couldn't reduce it so this wasn't used in a time literal context. @@ -5300,9 +5309,16 @@ type Valuer interface { Value(key string) (interface{}, bool) } +// ZoneValuer is the interface that specifies the current time zone. +type ZoneValuer interface { + // Zone returns the time zone location. + Zone() *time.Location +} + // NowValuer returns only the value for "now()". type NowValuer struct { - Now time.Time + Now time.Time + Location *time.Location } // Value is a method that returns the value and existence flag for a given key. @@ -5313,6 +5329,14 @@ func (v *NowValuer) Value(key string) (interface{}, bool) { return nil, false } +// Zone is a method that returns the time.Location. +func (v *NowValuer) Zone() *time.Location { + if v.Location != nil { + return v.Location + } + return time.UTC +} + // ContainsVarRef returns true if expr is a VarRef or contains one. func ContainsVarRef(expr Expr) bool { var v containsVarRefVisitor diff --git a/influxql/ast_test.go b/influxql/ast_test.go index 99f26ddfb81..481fd2d4d2e 100644 --- a/influxql/ast_test.go +++ b/influxql/ast_test.go @@ -863,6 +863,7 @@ func TestTimeRange(t *testing.T) { for i, tt := range []struct { expr string min, max, err string + loc string }{ // LHS VarRef {expr: `time > '2000-01-01 00:00:00'`, min: `2000-01-01T00:00:00.000000001Z`, max: `0001-01-01T00:00:00Z`}, @@ -901,23 +902,39 @@ func TestTimeRange(t *testing.T) { {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`}, {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`}, {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`}, + + // Time zone expressions. + {expr: `time >= '2000-01-01'`, loc: `America/Los_Angeles`, min: `2000-01-01T00:00:00-08:00`, max: `0001-01-01T00:00:00Z`}, + {expr: `time <= '2000-01-01'`, loc: `America/Los_Angeles`, min: `0001-01-01T00:00:00Z`, max: `2000-01-01T00:00:00-08:00`}, + {expr: `time >= '2000-01-01 03:17:00'`, loc: `America/Los_Angeles`, min: `2000-01-01T03:17:00-08:00`, max: `0001-01-01T00:00:00Z`}, + {expr: `time <= '2000-01-01 03:17:00'`, loc: `America/Los_Angeles`, min: `0001-01-01T00:00:00Z`, max: `2000-01-01T03:17:00-08:00`}, } { - // Extract time range. - expr := MustParseExpr(tt.expr) - min, max, err := influxql.TimeRange(expr) + t.Run(tt.expr, func(t *testing.T) { + // Load the time zone if one was specified. + var loc *time.Location + if tt.loc != "" { + l, err := time.LoadLocation(tt.loc) + if err != nil { + t.Fatalf("unable to load time zone %s: %s", tt.loc, err) + } + loc = l + } - // Compare with expected min/max. - if min := min.Format(time.RFC3339Nano); tt.min != min { - t.Errorf("%d. %s: unexpected min:\n\nexp=%s\n\ngot=%s\n\n", i, tt.expr, tt.min, min) - continue - } - if max := max.Format(time.RFC3339Nano); tt.max != max { - t.Errorf("%d. %s: unexpected max:\n\nexp=%s\n\ngot=%s\n\n", i, tt.expr, tt.max, max) - continue - } - 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) - } + // Extract time range. + expr := MustParseExpr(tt.expr) + min, max, err := influxql.TimeRange(expr, loc) + + // Compare with expected min/max. + if min := min.Format(time.RFC3339Nano); tt.min != min { + t.Fatalf("%d. %s: unexpected min:\n\nexp=%s\n\ngot=%s\n\n", i, tt.expr, tt.min, min) + } + if max := max.Format(time.RFC3339Nano); tt.max != max { + t.Fatalf("%d. %s: unexpected max:\n\nexp=%s\n\ngot=%s\n\n", i, tt.expr, tt.max, max) + } + if (err != nil && err.Error() != tt.err) || (err == nil && tt.err != "") { + t.Fatalf("%d. %s: unexpected error:\n\nexp=%s\n\ngot=%s\n\n", i, tt.expr, tt.err, err) + } + }) } } @@ -1808,7 +1825,7 @@ func (o Valuer) Value(key string) (v interface{}, ok bool) { // MustTimeRange will parse a time range. Panic on error. func MustTimeRange(expr influxql.Expr) (min, max time.Time) { - min, max, err := influxql.TimeRange(expr) + min, max, err := influxql.TimeRange(expr, nil) if err != nil { panic(err) } diff --git a/influxql/iterator.go b/influxql/iterator.go index 4d6af718899..f89fca6c6cd 100644 --- a/influxql/iterator.go +++ b/influxql/iterator.go @@ -703,7 +703,7 @@ type IteratorOptions struct { func newIteratorOptionsStmt(stmt *SelectStatement, sopt *SelectOptions) (opt IteratorOptions, err error) { // Determine time range from the condition. - startTime, endTime, err := TimeRange(stmt.Condition) + startTime, endTime, err := TimeRange(stmt.Condition, stmt.Location) if err != nil { return IteratorOptions{}, err } diff --git a/services/continuous_querier/service_test.go b/services/continuous_querier/service_test.go index b8442a52491..603f5c7d075 100644 --- a/services/continuous_querier/service_test.go +++ b/services/continuous_querier/service_test.go @@ -123,7 +123,7 @@ func TestContinuousQueryService_ResampleOptions(t *testing.T) { s.QueryExecutor.StatementExecutor = &StatementExecutor{ ExecuteStatementFn: func(stmt influxql.Statement, ctx influxql.ExecutionContext) error { s := stmt.(*influxql.SelectStatement) - min, max, err := influxql.TimeRange(s.Condition) + min, max, err := influxql.TimeRange(s.Condition, s.Location) if err != nil { t.Errorf("unexpected error parsing time range: %s", err) } else if !expected.min.Equal(min) || !expected.max.Equal(max) { @@ -204,7 +204,7 @@ func TestContinuousQueryService_EveryHigherThanInterval(t *testing.T) { s.QueryExecutor.StatementExecutor = &StatementExecutor{ ExecuteStatementFn: func(stmt influxql.Statement, ctx influxql.ExecutionContext) error { s := stmt.(*influxql.SelectStatement) - min, max, err := influxql.TimeRange(s.Condition) + min, max, err := influxql.TimeRange(s.Condition, s.Location) if err != nil { t.Errorf("unexpected error parsing time range: %s", err) } else if !expected.min.Equal(min) || !expected.max.Equal(max) { @@ -273,7 +273,7 @@ func TestContinuousQueryService_GroupByOffset(t *testing.T) { s.QueryExecutor.StatementExecutor = &StatementExecutor{ ExecuteStatementFn: func(stmt influxql.Statement, ctx influxql.ExecutionContext) error { s := stmt.(*influxql.SelectStatement) - min, max, err := influxql.TimeRange(s.Condition) + min, max, err := influxql.TimeRange(s.Condition, s.Location) if err != nil { t.Errorf("unexpected error parsing time range: %s", err) } else if !expected.min.Equal(min) || !expected.max.Equal(max) { @@ -433,7 +433,7 @@ func TestExecuteContinuousQuery_TimeRange(t *testing.T) { s.QueryExecutor.StatementExecutor = &StatementExecutor{ ExecuteStatementFn: func(stmt influxql.Statement, ctx influxql.ExecutionContext) error { s := stmt.(*influxql.SelectStatement) - min, max, err := influxql.TimeRange(s.Condition) + min, max, err := influxql.TimeRange(s.Condition, s.Location) max = max.Add(time.Nanosecond) if err != nil { t.Errorf("unexpected error parsing time range: %s", err) @@ -547,7 +547,7 @@ func TestExecuteContinuousQuery_TimeZone(t *testing.T) { ExecuteStatementFn: func(stmt influxql.Statement, ctx influxql.ExecutionContext) error { test := <-tests s := stmt.(*influxql.SelectStatement) - min, max, err := influxql.TimeRange(s.Condition) + min, max, err := influxql.TimeRange(s.Condition, s.Location) max = max.Add(time.Nanosecond) if err != nil { t.Errorf("unexpected error parsing time range: %s", err)