Skip to content
Permalink
Browse files
feat(spanner/spansql): support for parsing generated columns (#3373)
Fixes #3255.
  • Loading branch information
dsymonds committed Dec 4, 2020
1 parent a19e567 commit 9b1d06fc90a4c07899c641a893dba0b47a1cead9
Showing with 50 additions and 5 deletions.
  1. +4 −3 spanner/spannertest/README.md
  2. +3 −0 spanner/spansql/keywords.go
  3. +14 −1 spanner/spansql/parser.go
  4. +22 −1 spanner/spansql/parser_test.go
  5. +3 −0 spanner/spansql/sql.go
  6. +2 −0 spanner/spansql/sql_test.go
  7. +2 −0 spanner/spansql/types.go
@@ -21,15 +21,16 @@ by ascending esotericism:
- more aggregation functions
- SELECT HAVING
- case insensitivity
- multiple joins
- alternate literal types (esp. strings)
- STRUCT types
- transaction simulation
- generated columns
- expression type casting, coercion
- multiple joins
- subselects
- transaction simulation
- FOREIGN KEY and CHECK constraints
- INSERT DML statements
- set operations (UNION, INTERSECT, EXCEPT)
- STRUCT types
- partition support
- conditional expressions
- table sampling (implementation)
@@ -143,5 +143,8 @@ var funcs = map[string]bool{
// Hash functions.
"SHA1": true,

// String functions.
"CHAR_LENGTH": true,

// TODO: many more
}
@@ -1373,7 +1373,7 @@ func (p *parser) parseColumnDef() (ColumnDef, *parseError) {

/*
column_def:
column_name {scalar_type | array_type} [NOT NULL] [options_def]
column_name {scalar_type | array_type} [NOT NULL] [AS ( expression ) STORED] [options_def]
*/

name, err := p.parseTableOrIndexOrColumnName()
@@ -1392,6 +1392,19 @@ func (p *parser) parseColumnDef() (ColumnDef, *parseError) {
cd.NotNull = true
}

if p.eat("AS", "(") {
cd.Generated, err = p.parseExpr()
if err != nil {
return ColumnDef{}, err
}
if err := p.expect(")"); err != nil {
return ColumnDef{}, err
}
if err := p.expect("STORED"); err != nil {
return ColumnDef{}, err
}
}

if p.sniff("OPTIONS") {
cd.Options, err = p.parseColumnOptions()
if err != nil {
@@ -379,6 +379,12 @@ func TestParseDDL(t *testing.T) {
Names ARRAY<STRING(MAX)>,
) PRIMARY KEY (Dummy);
-- Table with generated column.
CREATE TABLE GenCol (
Name STRING(MAX) NOT NULL,
NameLen INT64 AS (CHAR_LENGTH(Name)) STORED,
) PRIMARY KEY (Name);
-- Trailing comment at end of file.
`, &DDL{Filename: "filename", List: []DDLStmt{
&CreateTable{
@@ -517,6 +523,19 @@ func TestParseDDL(t *testing.T) {
PrimaryKey: []KeyPart{{Column: "Dummy"}},
Position: line(35),
},
&CreateTable{
Name: "GenCol",
Columns: []ColumnDef{
{Name: "Name", Type: Type{Base: String, Len: MaxLen}, NotNull: true, Position: line(45)},
{
Name: "NameLen", Type: Type{Base: Int64},
Generated: Func{Name: "CHAR_LENGTH", Args: []Expr{ID("Name")}},
Position: line(46),
},
},
PrimaryKey: []KeyPart{{Column: "Name"}},
Position: line(44),
},
}, Comments: []*Comment{
{Marker: "#", Start: line(2), End: line(2),
Text: []string{"This is a comment."}},
@@ -535,8 +554,10 @@ func TestParseDDL(t *testing.T) {
{Marker: "--", Start: line(37), End: line(37), Text: []string{"comment on ids"}},
{Marker: "--", Isolated: true, Start: line(38), End: line(38), Text: []string{"leading multi comment immediately after inline comment"}},

{Marker: "--", Isolated: true, Start: line(43), End: line(43), Text: []string{"Table with generated column."}},

// Comment after everything else.
{Marker: "--", Isolated: true, Start: line(43), End: line(43), Text: []string{"Trailing comment at end of file."}},
{Marker: "--", Isolated: true, Start: line(49), End: line(49), Text: []string{"Trailing comment at end of file."}},
}}},
// No trailing comma:
{`ALTER TABLE T ADD COLUMN C2 INT64`, &DDL{Filename: "filename", List: []DDLStmt{
@@ -180,6 +180,9 @@ func (cd ColumnDef) SQL() string {
if cd.NotNull {
str += " NOT NULL"
}
if cd.Generated != nil {
str += " AS (" + cd.Generated.SQL() + ") STORED"
}
if cd.Options != (ColumnOptions{}) {
str += " " + cd.Options.SQL()
}
@@ -75,6 +75,7 @@ func TestSQL(t *testing.T) {
{Name: "Cj", Type: Type{Array: true, Base: Int64}, Position: line(11)},
{Name: "Ck", Type: Type{Array: true, Base: String, Len: MaxLen}, Position: line(12)},
{Name: "Cl", Type: Type{Base: Timestamp}, Options: ColumnOptions{AllowCommitTimestamp: boolAddr(false)}, Position: line(13)},
{Name: "Cm", Type: Type{Base: Int64}, Generated: Func{Name: "CHAR_LENGTH", Args: []Expr{ID("Ce")}}, Position: line(14)},
},
PrimaryKey: []KeyPart{
{Column: "Ca"},
@@ -95,6 +96,7 @@ func TestSQL(t *testing.T) {
Cj ARRAY<INT64>,
Ck ARRAY<STRING(MAX)>,
Cl TIMESTAMP OPTIONS (allow_commit_timestamp = null),
Cm INT64 AS (CHAR_LENGTH(Ce)) STORED,
) PRIMARY KEY(Ca, Cb DESC)`,
reparseDDL,
},
@@ -244,6 +244,8 @@ type ColumnDef struct {
Type Type
NotNull bool

Generated Expr // set of this is a generated column

Options ColumnOptions

Position Position // position of the column name

0 comments on commit 9b1d06f

Please sign in to comment.