Fix failing selects on views defined with date literals (#2313)

* Fix failing selects on views defined with date literals
* System variables implementation.
This commit is contained in:
Vengata Krishnan 2023-03-17 12:24:00 -04:00 committed by GitHub
parent 9c082c5c77
commit 2c3be9d1e8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 141 additions and 13 deletions

View file

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

View file

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

View file

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

View file

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

View file

@ -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<CURRENT_TIMESTAMP or a_date<CURRENT_DATE or a_date<'2023-03-15T00:00:00Z';",
),
ExpHdrs: hdrs(),
ExpRows: rows(),
Compare: CompareExactUnordered,
SortStringKeys: true,
},
{
name: "select-view-with-built-in-literals",
SQLs: sqls(
"select * from viewwithliteral;",
"select _id, a_string, a_int, a_date from viewwithliteral;",
),
ExpHdrs: hdrs(
hdr("_id", fldTypeID),
hdr("a_string", fldTypeString),
hdr("a_int", fldTypeInt),
hdr("a_date", fldTypeTimestamp),
),
ExpRows: rows(
row(int64(1), "str1", int64(10), time.Unix(0, 0).UTC()),
row(int64(2), "str1", int64(20), time.Unix(0, 0).UTC()),
row(int64(3), "str2", int64(30), time.Unix(0, 0).UTC()),
row(int64(4), "str2", int64(40), time.Unix(0, 0).UTC()),
row(int64(5), "str3", int64(50), time.Unix(0, 0).UTC()),
),
Compare: CompareExactUnordered,
SortStringKeys: true,
},
},
}