Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(spanner/spansql): support for parsing generated columns #3373

Merged
merged 1 commit into from Dec 4, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 4 additions & 3 deletions spanner/spannertest/README.md
Expand Up @@ -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)
3 changes: 3 additions & 0 deletions spanner/spansql/keywords.go
Expand Up @@ -143,5 +143,8 @@ var funcs = map[string]bool{
// Hash functions.
"SHA1": true,

// String functions.
"CHAR_LENGTH": true,

// TODO: many more
}
15 changes: 14 additions & 1 deletion spanner/spansql/parser.go
Expand Up @@ -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()
Expand All @@ -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 {
Expand Down
23 changes: 22 additions & 1 deletion spanner/spansql/parser_test.go
Expand Up @@ -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{
Expand Down Expand Up @@ -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."}},
Expand All @@ -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{
Expand Down
3 changes: 3 additions & 0 deletions spanner/spansql/sql.go
Expand Up @@ -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()
}
Expand Down
2 changes: 2 additions & 0 deletions spanner/spansql/sql_test.go
Expand Up @@ -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"},
Expand All @@ -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,
},
Expand Down
2 changes: 2 additions & 0 deletions spanner/spansql/types.go
Expand Up @@ -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
Expand Down