Skip to content

Commit

Permalink
fix(spanner/spansql): fix DATE and TIMESTAMP parsing. (#4480)
Browse files Browse the repository at this point in the history
* fix(spanner/spansql): fix DATE, TIMESTAMP parse.

This enables to parse below cases.
- DATE, TIMESTAMP used as identifier.(these words does not reserved
  keyword.)
- DATE, TIMESTAMP used as function.

* fix(spanner/spansql): fix allFuncs index, and add more Date, Timestamp
functions

* fix(spanner/spansql): fix parse DATE, Timestamp literals.

This modified parsing DATE, TIMESTAMP that if next token is string
litelal, then current token treated as DATE, TIMESTAMP literals.
  • Loading branch information
Takeshi Nakata committed Jul 23, 2021
1 parent 6af6353 commit dec7a67
Show file tree
Hide file tree
Showing 3 changed files with 94 additions and 4 deletions.
31 changes: 31 additions & 0 deletions spanner/spansql/keywords.go
Expand Up @@ -190,4 +190,35 @@ var allFuncs = []string{
"ARRAY_REVERSE",
"ARRAY_IS_DISTINCT",
"SAFE_OFFSET", "SAFE_ORDINAL",

// Date functions.
"CURRENT_DATE",
"EXTRACT",
"DATE",
"DATE_ADD",
"DATE_SUB",
"DATE_DIFF",
"DATE_TRUNC",
"DATE_FROM_UNIX_DATE",
"FORMAT_DATE",
"PARSE_DATE",
"UNIX_DATE",

// Timestamp functions.
"CURRENT_TIMESTAMP",
"STRING",
"TIMESTAMP",
"TIMESTAMP_ADD",
"TIMESTAMP_SUB",
"TIMESTAMP_DIFF",
"TIMESTAMP_TRUNC",
"FORMAT_TIMESTAMP",
"PARSE_TIMESTAMP",
"TIMESTAMP_SECONDS",
"TIMESTAMP_MILLIS",
"TIMESTAMP_MICROS",
"UNIX_SECONDS",
"UNIX_MILLIS",
"UNIX_MICROS",
"PENDING_COMMIT_TIMESTAMP",
}
23 changes: 19 additions & 4 deletions spanner/spansql/parser.go
Expand Up @@ -906,6 +906,17 @@ func (p *parser) sniff(want ...string) bool {
return true
}

// sniffTokenType reports whether the next token type is as specified.
func (p *parser) sniffTokenType(want tokenType) bool {
orig := *p
defer func() { *p = orig }()

if p.next().typ == want {
return true
}
return false
}

// eat reports whether the next N tokens are as specified,
// then consumes them.
func (p *parser) eat(want ...string) bool {
Expand Down Expand Up @@ -2714,11 +2725,15 @@ func (p *parser) parseLit() (Expr, *parseError) {
p.back()
return p.parseArrayLit()
case tok.caseEqual("DATE"):
p.back()
return p.parseDateLit()
if p.sniffTokenType(stringToken) {
p.back()
return p.parseDateLit()
}
case tok.caseEqual("TIMESTAMP"):
p.back()
return p.parseTimestampLit()
if p.sniffTokenType(stringToken) {
p.back()
return p.parseTimestampLit()
}
}

// TODO: struct literals
Expand Down
44 changes: 44 additions & 0 deletions spanner/spansql/parser_test.go
Expand Up @@ -83,6 +83,35 @@ func TestParseQuery(t *testing.T) {
},
},
},
{`SELECT date, timestamp as timestamp FROM Packages WHERE date = DATE '2014-09-27' AND timestamp = TIMESTAMP '2014-09-27 12:30:00'`,
Query{
Select: Select{
List: []Expr{ID("date"), ID("timestamp")},
From: []SelectFrom{SelectFromTable{Table: "Packages"}},
Where: LogicalOp{
Op: And,
LHS: ComparisonOp{
Op: Eq,
LHS: ID("date"),
RHS: DateLiteral{Year: 2014, Month: 9, Day: 27},
},
RHS: ComparisonOp{
Op: Eq,
LHS: ID("timestamp"),
RHS: TimestampLiteral(timef(t, "2006-01-02 15:04:05", "2014-09-27 12:30:00")),
},
},
ListAliases: []ID{"", "timestamp"},
},
},
},
{`SELECT UNIX_DATE(DATE "2008-12-25")`,
Query{
Select: Select{
List: []Expr{Func{Name: "UNIX_DATE", Args: []Expr{DateLiteral{Year: 2008, Month: 12, Day: 25}}}},
},
},
},
{`SELECT SUM(PointsScored) AS total_points, FirstName, LastName AS surname FROM PlayerStats GROUP BY FirstName, LastName`,
Query{
Select: Select{
Expand Down Expand Up @@ -336,7 +365,14 @@ func TestParseExpr(t *testing.T) {

// Date and timestamp literals:
{`DATE '2014-09-27'`, DateLiteral(civil.Date{Year: 2014, Month: time.September, Day: 27})},
{`TIMESTAMP '2014-09-27 12:30:00'`, TimestampLiteral(timef(t, "2006-01-02 15:04:05", "2014-09-27 12:30:00"))},

// date and timestamp funclit
{`DATE('2014-09-27')`, Func{Name: "DATE", Args: []Expr{StringLiteral("2014-09-27")}}},
{`TIMESTAMP('2014-09-27 12:30:00')`, Func{Name: "TIMESTAMP", Args: []Expr{StringLiteral("2014-09-27 12:30:00")}}},
// date and timestamp identifier
{`DATE = '2014-09-27'`, ComparisonOp{LHS: ID("DATE"), Op: Eq, RHS: StringLiteral("2014-09-27")}},
{`TIMESTAMP = '2014-09-27 12:30:00'`, ComparisonOp{LHS: ID("TIMESTAMP"), Op: Eq, RHS: StringLiteral("2014-09-27 12:30:00")}},
// Array literals:
// https://cloud.google.com/spanner/docs/lexical#array_literals
{`[1, 2, 3]`, Array{IntegerLiteral(1), IntegerLiteral(2), IntegerLiteral(3)}},
Expand Down Expand Up @@ -796,3 +832,11 @@ func TestParseFailures(t *testing.T) {
}
}
}

func timef(t *testing.T, format, s string) time.Time {
ti, err := time.ParseInLocation(format, string(s), defaultLocation)
if err != nil {
t.Errorf("parsing %s [%s] time.ParseInLocation failed.", s, format)
}
return ti
}

0 comments on commit dec7a67

Please sign in to comment.