From 2c3be9d1e8e645e12e1504ffb124d155cda34669 Mon Sep 17 00:00:00 2001 From: Vengata Krishnan <122897205+vkrishnanfb@users.noreply.github.com> Date: Fri, 17 Mar 2023 12:24:00 -0400 Subject: [PATCH] Fix failing selects on views defined with date literals (#2313) * Fix failing selects on views defined with date literals * System variables implementation. --- sql3/parser/ast.go | 42 ++++++++++++++++++++-- sql3/parser/parser.go | 8 ++--- sql3/planner/expression.go | 56 ++++++++++++++++++++++++++++++ sql3/planner/expressionanalyzer.go | 3 ++ sql3/test/defs/defs_views.go | 45 +++++++++++++++++++++--- 5 files changed, 141 insertions(+), 13 deletions(-) diff --git a/sql3/parser/ast.go b/sql3/parser/ast.go index 82cc2a123..8ee7377ed 100644 --- a/sql3/parser/ast.go +++ b/sql3/parser/ast.go @@ -58,6 +58,7 @@ func (*ForeignKeyConstraint) node() {} func (*FrameSpec) node() {} func (*Ident) node() {} func (*Variable) node() {} +func (*SysVariable) node() {} func (*IndexedColumn) node() {} func (*InsertStatement) node() {} func (*JoinClause) node() {} @@ -248,6 +249,7 @@ func (*Exists) expr() {} func (*ExprList) expr() {} func (*Ident) expr() {} func (*Variable) expr() {} +func (*SysVariable) expr() {} func (*NullLit) expr() {} func (*IntegerLit) expr() {} func (*FloatLit) expr() {} @@ -1686,6 +1688,42 @@ func IdentName(ident *Ident) string { return ident.Name } +// SysVariable represents built-in system variables that can be referenced in the sql for current date, current time and other potential pre-determinable values. +// In SQL these system provided data elements are referenced using keywords such as CURRENT_DATE & CURRENT_TIMESTAMP, etc. +type SysVariable struct { + NamePos Pos // variable position in sql + Token Token // parser token mapped to the variable's name/keyword +} + +func (*SysVariable) IsLiteral() bool { return false } + +func (svar *SysVariable) Pos() Pos { + return svar.NamePos +} + +func (svar *SysVariable) Clone() *SysVariable { + if svar == nil { + return nil + } + other := *svar + return &other +} +func (svar *SysVariable) Name() string { + return tokens[svar.Token] +} + +func (svar *SysVariable) String() string { + return svar.Name() +} + +func (svar *SysVariable) DataType() ExprDataType { + switch svar.Token { + case CURRENT_DATE, CURRENT_TIMESTAMP: + return NewDataTypeTimestamp() + } + return nil +} + type Variable struct { NamePos Pos // variable position Name string // variable name @@ -1940,9 +1978,9 @@ func (lit *DateLit) Clone() *DateLit { return &other } -// String returns the string representation of the expression. +// String returns the string representation of the Datetime value. func (lit *DateLit) String() string { - return lit.Value.Format(time.RFC3339) + return "'" + lit.Value.Format(time.RFC3339) + "'" } type UnaryExpr struct { diff --git a/sql3/parser/parser.go b/sql3/parser/parser.go index 32ad9c593..1427ab02f 100644 --- a/sql3/parser/parser.go +++ b/sql3/parser/parser.go @@ -5,7 +5,6 @@ import ( "fmt" "io" "strings" - "time" ) // Parser represents a SQL parser. @@ -2768,12 +2767,9 @@ func (p *Parser) parseOperand() (expr Expr, err error) { case NULL: return &NullLit{ValuePos: pos}, nil case CURRENT_DATE: - now := time.Now().UTC() - nowDate := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location()) - return &DateLit{ValuePos: pos, Value: nowDate}, nil + return &SysVariable{NamePos: pos, Token: tok}, nil case CURRENT_TIMESTAMP: - now := time.Now().UTC() - return &DateLit{ValuePos: pos, Value: now}, nil + return &SysVariable{NamePos: pos, Token: tok}, nil case TRUE, FALSE: return &BoolLit{ValuePos: pos, Value: tok == TRUE}, nil case PLUS, MINUS, BITNOT: diff --git a/sql3/planner/expression.go b/sql3/planner/expression.go index 1c6c6a92a..0457f2f32 100644 --- a/sql3/planner/expression.go +++ b/sql3/planner/expression.go @@ -1998,6 +1998,59 @@ func (n *boolLiteralPlanExpression) WithChildren(children ...types.PlanExpressio return n, nil } +// represents system variables such as CURRENT_DATE and CURRENT_DATETIME +type sysVariablePlanExpression struct { + name string // name of the system variable + token parser.Token // token mapped to the system variable name +} + +func newSysVariablePlanExpression(name string, token parser.Token) *sysVariablePlanExpression { + return &sysVariablePlanExpression{ + name: name, + token: token, + } +} + +func (n *sysVariablePlanExpression) Evaluate(currentRow []interface{}) (interface{}, error) { + switch n.token { + case parser.CURRENT_DATE: + dt := time.Now().UTC() + return time.Date(dt.Year(), dt.Month(), dt.Day(), 0, 0, 0, 0, dt.Location()), nil + case parser.CURRENT_TIMESTAMP: + return time.Now().UTC(), nil + } + return nil, sql3.NewErrInternal(fmt.Sprintf("Mising plan expression implementation for system variable '%s'", n.name)) +} + +func (n *sysVariablePlanExpression) Type() parser.ExprDataType { + switch n.token { + case parser.CURRENT_DATE, parser.CURRENT_TIMESTAMP: + return parser.NewDataTypeTimestamp() + } + return nil +} + +func (n *sysVariablePlanExpression) String() string { + return n.name +} + +func (n *sysVariablePlanExpression) Plan() map[string]interface{} { + result := make(map[string]interface{}) + result["_expr"] = fmt.Sprintf("%T", n) + result["description"] = n.String() + result["dataType"] = n.Type().TypeDescription() + result["value"], _ = n.Evaluate(nil) + return result +} + +func (n *sysVariablePlanExpression) Children() []types.PlanExpression { + return []types.PlanExpression{} +} + +func (n *sysVariablePlanExpression) WithChildren(children ...types.PlanExpression) (types.PlanExpression, error) { + return n, nil +} + // dateLiteralPlanExpression is a date literal type dateLiteralPlanExpression struct { value time.Time @@ -2611,6 +2664,9 @@ func (p *ExecutionPlanner) compileExpr(expr parser.Expr) (_ types.PlanExpression case *parser.DateLit: return newDateLiteralPlanExpression(expr.Value), nil + case *parser.SysVariable: + return newSysVariablePlanExpression(expr.Name(), expr.Token), nil + case *parser.ParenExpr: return p.compileExpr(expr.X) diff --git a/sql3/planner/expressionanalyzer.go b/sql3/planner/expressionanalyzer.go index 78aa5d5b7..1adf414c4 100644 --- a/sql3/planner/expressionanalyzer.go +++ b/sql3/planner/expressionanalyzer.go @@ -151,6 +151,9 @@ func (p *ExecutionPlanner) analyzeExpression(ctx context.Context, expr parser.Ex case *parser.DateLit: return e, nil + case *parser.SysVariable: + return e, nil + case *parser.ParenExpr: pexpr, err := p.analyzeExpression(ctx, e.X, scope) if err != nil { diff --git a/sql3/test/defs/defs_views.go b/sql3/test/defs/defs_views.go index bd894bbb6..a6d9ee257 100644 --- a/sql3/test/defs/defs_views.go +++ b/sql3/test/defs/defs_views.go @@ -1,5 +1,7 @@ package defs +import "time" + var viewTests = TableTest{ name: "viewtests", Table: tbl( @@ -8,13 +10,14 @@ var viewTests = TableTest{ srcHdr("_id", fldTypeID), srcHdr("a_string", fldTypeString), srcHdr("a_int", fldTypeInt), + srcHdr("a_date", fldTypeTimestamp), ), srcRows( - srcRow(int64(1), "str1", int64(10)), - srcRow(int64(2), "str1", int64(20)), - srcRow(int64(3), "str2", int64(30)), - srcRow(int64(4), "str2", int64(40)), - srcRow(int64(5), "str3", int64(50)), + srcRow(int64(1), "str1", int64(10), time.Unix(0, 0).UTC()), + srcRow(int64(2), "str1", int64(20), time.Unix(0, 0).UTC()), + srcRow(int64(3), "str2", int64(30), time.Unix(0, 0).UTC()), + srcRow(int64(4), "str2", int64(40), time.Unix(0, 0).UTC()), + srcRow(int64(5), "str3", int64(50), time.Unix(0, 0).UTC()), ), ), SQLTests: []SQLTest{ @@ -122,5 +125,37 @@ var viewTests = TableTest{ ), ExpErr: "table or view 'viewonviewtable' not found", }, + { + name: "create-view-with-built-in-literals", + SQLs: sqls( + "create view if not exists viewwithliteral as select _id, a_string, a_int, a_date from viewtable where a_date