Parse time literals using the time zone in the select statement

pull/8642/head
Jonathan A. Sternberg 2017-07-27 11:50:06 -05:00
parent ac2a9690a1
commit 950753d036
6 changed files with 109 additions and 67 deletions

View File

@ -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]

View File

@ -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
}

View File

@ -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 == <TimeLiteral>" 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

View File

@ -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`},
} {
// Extract time range.
expr := MustParseExpr(tt.expr)
min, max, err := influxql.TimeRange(expr)
// 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)
}
// 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`},
} {
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
}
// 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)
}

View File

@ -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
}

View File

@ -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)