diff --git a/spanner/spansql/parser.go b/spanner/spansql/parser.go index 295ec465eb6..b22ffc78dc2 100644 --- a/spanner/spansql/parser.go +++ b/spanner/spansql/parser.go @@ -2975,6 +2975,11 @@ func (p *parser) parseLit() (Expr, *parseError) { p.back() return p.parseTimestampLit() } + case tok.caseEqual("JSON"): + if p.sniffTokenType(stringToken) { + p.back() + return p.parseJSONLit() + } } // TODO: struct literals @@ -3140,6 +3145,19 @@ func (p *parser) parseTimestampLit() (TimestampLiteral, *parseError) { return TimestampLiteral{}, p.errorf("invalid timestamp literal %q", s) } +func (p *parser) parseJSONLit() (JSONLiteral, *parseError) { + if err := p.expect("JSON"); err != nil { + return JSONLiteral{}, err + } + s, err := p.parseStringLit() + if err != nil { + return JSONLiteral{}, err + } + // It is not guaranteed that the returned JSONLiteral is a valid JSON document + // to avoid error due to parsing SQL generated with an invalid JSONLiteral like JSONLiteral("") + return JSONLiteral(s), nil +} + func (p *parser) parseStringLit() (StringLiteral, *parseError) { tok := p.next() if tok.err != nil { diff --git a/spanner/spansql/parser_test.go b/spanner/spansql/parser_test.go index 3cf64f0846f..ab8048a0797 100644 --- a/spanner/spansql/parser_test.go +++ b/spanner/spansql/parser_test.go @@ -427,6 +427,9 @@ func TestParseExpr(t *testing.T) { {`[1, 2, 3]`, Array{IntegerLiteral(1), IntegerLiteral(2), IntegerLiteral(3)}}, {`['x', 'y', 'xy']`, Array{StringLiteral("x"), StringLiteral("y"), StringLiteral("xy")}}, {`ARRAY[1, 2, 3]`, Array{IntegerLiteral(1), IntegerLiteral(2), IntegerLiteral(3)}}, + // JSON literals: + // https://cloud.google.com/spanner/docs/reference/standard-sql/lexical#json_literals + {`JSON '{"a": 1}'`, JSONLiteral(`{"a": 1}`)}, // OR is lower precedence than AND. {`A AND B OR C`, LogicalOp{LHS: LogicalOp{LHS: ID("A"), Op: And, RHS: ID("B")}, Op: Or, RHS: ID("C")}}, diff --git a/spanner/spansql/sql.go b/spanner/spansql/sql.go index 119c44145b8..b82b992df20 100644 --- a/spanner/spansql/sql.go +++ b/spanner/spansql/sql.go @@ -738,3 +738,8 @@ func (tl TimestampLiteral) SQL() string { return buildSQL(tl) } func (tl TimestampLiteral) addSQL(sb *strings.Builder) { fmt.Fprintf(sb, "TIMESTAMP '%s'", time.Time(tl).Format("2006-01-02 15:04:05.000000 -07:00")) } + +func (jl JSONLiteral) SQL() string { return buildSQL(jl) } +func (jl JSONLiteral) addSQL(sb *strings.Builder) { + fmt.Fprintf(sb, "JSON '%s'", jl) +} diff --git a/spanner/spansql/sql_test.go b/spanner/spansql/sql_test.go index b82cde1aab0..fb7a85a7082 100644 --- a/spanner/spansql/sql_test.go +++ b/spanner/spansql/sql_test.go @@ -559,6 +559,11 @@ func TestSQL(t *testing.T) { `TIMESTAMP '2014-09-27 12:34:56.123456 -07:00'`, reparseExpr, }, + { + JSONLiteral(`{"a": 1}`), + `JSON '{"a": 1}'`, + reparseExpr, + }, { Query{ Select: Select{ diff --git a/spanner/spansql/types.go b/spanner/spansql/types.go index 6b125bd7817..521df50b752 100644 --- a/spanner/spansql/types.go +++ b/spanner/spansql/types.go @@ -771,6 +771,12 @@ type TimestampLiteral time.Time func (TimestampLiteral) isExpr() {} +// JSONLiteral represents a JSON literal +// https://cloud.google.com/spanner/docs/reference/standard-sql/lexical#json_literals +type JSONLiteral []byte + +func (JSONLiteral) isExpr() {} + type StarExpr int // Star represents a "*" in an expression.