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 DEFAULT keyword #5932

Merged
merged 5 commits into from Apr 27, 2022
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
1 change: 1 addition & 0 deletions spanner/spannertest/README.md
Expand Up @@ -23,6 +23,7 @@ by ascending esotericism:
- more aggregation functions
- SELECT HAVING
- more literal types
- DEFAULT
- expressions that return null for generated columns
- generated columns referencing other generated columns
- checking dependencies on a generated column before deleting a column
Expand Down
44 changes: 42 additions & 2 deletions spanner/spansql/parser.go
Expand Up @@ -1528,7 +1528,7 @@ func (p *parser) parseColumnDef() (ColumnDef, *parseError) {

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

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

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

if p.eat("AS", "(") {
cd.Generated, err = p.parseExpr()
if err != nil {
Expand All @@ -1573,9 +1583,29 @@ func (p *parser) parseColumnDef() (ColumnDef, *parseError) {
func (p *parser) parseColumnAlteration() (ColumnAlteration, *parseError) {
debugf("parseColumnAlteration: %v", p)
/*
{ data_type } [ NOT NULL ] | SET [ options_def ]
{
data_type [ NOT NULL ] [ DEFAULT ( expression ) ]
| SET ( options_def )
| SET DEFAULT ( expression )
| DROP DEFAULT
}
*/

if p.eat("SET", "DEFAULT", "(") {
d, err := p.parseExpr()
if err != nil {
return nil, err
}
if err := p.expect(")"); err != nil {
return nil, err
}
return SetDefault{Default: d}, nil
}

if p.eat("DROP", "DEFAULT") {
return DropDefault{}, nil
}

if p.eat("SET") {
co, err := p.parseColumnOptions()
if err != nil {
Expand All @@ -1594,6 +1624,16 @@ func (p *parser) parseColumnAlteration() (ColumnAlteration, *parseError) {
sct.NotNull = true
}

if p.eat("DEFAULT", "(") {
sct.Default, err = p.parseExpr()
if err != nil {
return nil, err
}
if err := p.expect(")"); err != nil {
return nil, err
}
}

return sct, nil
}

Expand Down
56 changes: 55 additions & 1 deletion spanner/spansql/parser_test.go
Expand Up @@ -552,6 +552,16 @@ func TestParseDDL(t *testing.T) {
shard_id INT64 AS (MOD(FARM_FINGERPRINT(user_id), 19)) STORED,
) PRIMARY KEY(user_id);

-- Table has a column with a default value.
CREATE TABLE DefaultCol (
Name STRING(MAX) NOT NULL,
Age INT64 DEFAULT (0),
) PRIMARY KEY (Name);

ALTER TABLE DefaultCol ALTER COLUMN Age DROP DEFAULT;
ALTER TABLE DefaultCol ALTER COLUMN Age SET DEFAULT (0);
ALTER TABLE DefaultCol ALTER COLUMN Age STRING(MAX) DEFAULT ("0");

-- Trailing comment at end of file.
`, &DDL{Filename: "filename", List: []DDLStmt{
&CreateTable{
Expand Down Expand Up @@ -795,6 +805,49 @@ func TestParseDDL(t *testing.T) {
PrimaryKey: []KeyPart{{Column: "user_id"}},
Position: line(66),
},

&CreateTable{
Name: "DefaultCol",
Columns: []ColumnDef{
{Name: "Name", Type: Type{Base: String, Len: MaxLen}, NotNull: true, Position: line(77)},
{
Name: "Age", Type: Type{Base: Int64},
Default: IntegerLiteral(0),
Position: line(78),
},
},
PrimaryKey: []KeyPart{{Column: "Name"}},
Position: line(76),
},
&AlterTable{
Name: "DefaultCol",
Alteration: AlterColumn{
Name: "Age",
Alteration: DropDefault{},
},
Position: line(81),
},
&AlterTable{
Name: "DefaultCol",
Alteration: AlterColumn{
Name: "Age",
Alteration: SetDefault{
Default: IntegerLiteral(0),
},
},
Position: line(82),
},
&AlterTable{
Name: "DefaultCol",
Alteration: AlterColumn{
Name: "Age",
Alteration: SetColumnType{
Type: Type{Base: String, Len: MaxLen},
Default: StringLiteral("0"),
},
},
Position: line(83),
},
}, Comments: []*Comment{
{Marker: "#", Start: line(2), End: line(2),
Text: []string{"This is a comment."}},
Expand All @@ -815,9 +868,10 @@ func TestParseDDL(t *testing.T) {

{Marker: "--", Isolated: true, Start: line(43), End: line(43), Text: []string{"Table with generated column."}},
{Marker: "--", Isolated: true, Start: line(49), End: line(49), Text: []string{"Table with row deletion policy."}},
{Marker: "--", Isolated: true, Start: line(75), End: line(75), Text: []string{"Table has a column with a default value."}},

// Comment after everything else.
{Marker: "--", Isolated: true, Start: line(75), End: line(75), Text: []string{"Trailing comment at end of file."}},
{Marker: "--", Isolated: true, Start: line(85), End: line(85), 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
14 changes: 14 additions & 0 deletions spanner/spansql/sql.go
Expand Up @@ -162,6 +162,9 @@ func (sct SetColumnType) SQL() string {
if sct.NotNull {
str += " NOT NULL"
}
if sct.Default != nil {
str += " DEFAULT (" + sct.Default.SQL() + ")"
}
return str
}

Expand All @@ -170,6 +173,14 @@ func (sco SetColumnOptions) SQL() string {
return "SET " + sco.Options.SQL()
}

func (sd SetDefault) SQL() string {
return "SET DEFAULT (" + sd.Default.SQL() + ")"
}

func (dp DropDefault) SQL() string {
return "DROP DEFAULT"
}

func (co ColumnOptions) SQL() string {
str := "OPTIONS ("
if co.AllowCommitTimestamp != nil {
Expand Down Expand Up @@ -254,6 +265,9 @@ func (cd ColumnDef) SQL() string {
if cd.NotNull {
str += " NOT NULL"
}
if cd.Default != nil {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(cd ColumnDef) SQL() generates an invalid SQL like ... DEFAULT (...) AS (...) STORED when both of Default and Generated are not nil. I thought about adding a enum-like const that has Default, Generated or None as a value but I didn't implement that because it will break compatibility.

str += " DEFAULT (" + cd.Default.SQL() + ")"
}
if cd.Generated != nil {
str += " AS (" + cd.Generated.SQL() + ") STORED"
}
Expand Down
44 changes: 44 additions & 0 deletions spanner/spansql/sql_test.go
Expand Up @@ -85,6 +85,7 @@ func TestSQL(t *testing.T) {
{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)},
{Name: "Cn", Type: Type{Base: JSON}, Position: line(15)},
{Name: "Co", Type: Type{Base: Int64}, Default: IntegerLiteral(1), Position: line(16)},
},
PrimaryKey: []KeyPart{
{Column: "Ca"},
Expand All @@ -107,6 +108,7 @@ func TestSQL(t *testing.T) {
Cl TIMESTAMP OPTIONS (allow_commit_timestamp = null),
Cm INT64 AS (CHAR_LENGTH(Ce)) STORED,
Cn JSON,
Co INT64 DEFAULT (1),
) PRIMARY KEY(Ca, Cb DESC)`,
reparseDDL,
},
Expand Down Expand Up @@ -266,6 +268,22 @@ func TestSQL(t *testing.T) {
"ALTER TABLE Ta ALTER COLUMN Cg STRING(MAX)",
reparseDDL,
},
{
&AlterTable{
Name: "Ta",
Alteration: AlterColumn{
Name: "Ch",
Alteration: SetColumnType{
Type: Type{Base: String, Len: MaxLen},
NotNull: true,
Default: StringLiteral("1"),
},
},
Position: line(1),
},
"ALTER TABLE Ta ALTER COLUMN Ch STRING(MAX) NOT NULL DEFAULT (\"1\")",
reparseDDL,
},
{
&AlterTable{
Name: "Ta",
Expand All @@ -282,6 +300,32 @@ func TestSQL(t *testing.T) {
"ALTER TABLE Ta ALTER COLUMN Ci SET OPTIONS (allow_commit_timestamp = null)",
reparseDDL,
},
{
&AlterTable{
Name: "Ta",
Alteration: AlterColumn{
Name: "Cj",
Alteration: SetDefault{
Default: StringLiteral("1"),
},
},
Position: line(1),
},
"ALTER TABLE Ta ALTER COLUMN Cj SET DEFAULT (\"1\")",
reparseDDL,
},
{
&AlterTable{
Name: "Ta",
Alteration: AlterColumn{
Name: "Ck",
Alteration: DropDefault{},
},
Position: line(1),
},
"ALTER TABLE Ta ALTER COLUMN Ck DROP DEFAULT",
reparseDDL,
},
{
&AlterTable{
Name: "WithRowDeletionPolicy",
Expand Down
10 changes: 10 additions & 0 deletions spanner/spansql/types.go
Expand Up @@ -235,14 +235,23 @@ type ColumnAlteration interface {

func (SetColumnType) isColumnAlteration() {}
func (SetColumnOptions) isColumnAlteration() {}
func (SetDefault) isColumnAlteration() {}
func (DropDefault) isColumnAlteration() {}

type SetColumnType struct {
Type Type
NotNull bool
Default Expr
}

type SetColumnOptions struct{ Options ColumnOptions }

type SetDefault struct {
Default Expr
}

type DropDefault struct{}

type OnDelete int

const (
Expand Down Expand Up @@ -320,6 +329,7 @@ type ColumnDef struct {
Type Type
NotNull bool

Default Expr // set if this column has a default value
Generated Expr // set of this is a generated column

Options ColumnOptions
Expand Down