Skip to content

Commit

Permalink
Implement SYNONYM in CREATE TABLE (#172)
Browse files Browse the repository at this point in the history
* Implement SYNONYM in CREATE TABLE

* Update testdata

* Fix redundant whitespace in (*Synonym).SQL()

* Apply suggestions from code review

Co-authored-by: Hiroya Fujinami <[email protected]>

---------

Co-authored-by: Hiroya Fujinami <[email protected]>
  • Loading branch information
apstndb and makenowjust authored Oct 24, 2024
1 parent 2fef70c commit ff8609d
Show file tree
Hide file tree
Showing 30 changed files with 458 additions and 39 deletions.
23 changes: 19 additions & 4 deletions ast/ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -1636,15 +1636,16 @@ type CreateDatabase struct {
// CreateTable is CREATE TABLE statement node.
//
// CREATE TABLE {{if .IfNotExists}}IF NOT EXISTS{{end}} {{.Name | sql}} (
// {{.Columns | sqlJoin ","}}
// {{if and .Columns .TableConstrains}},{{end}}{{.TableConstraints | sqlJoin ","}}
// {{.Columns | sqlJoin ","}}{{if and .Columns (or .TableConstrains .Synonym)}},{{end}}
// {{.TableConstraints | sqlJoin ","}}{{if and .TableConstraints .Synonym}},{{end}}
// {{.Synonym | sqlJoin ","}}
// )
// PRIMARY KEY ({{.PrimaryKeys | sqlJoin ","}})
// {{.Cluster | sqlOpt}}
// {{.CreateRowDeletionPolicy | sqlOpt}}
//
// Spanner SQL allows to mix `Columns` and `TableConstraints`, however they are
// separated in AST definition for historical reasons. If you want to get
// Spanner SQL allows to mix `Columns` and `TableConstraints` and `Synonyms`,
// however they are separated in AST definition for historical reasons. If you want to get
// the original order of them, please sort them by their `Pos()`.
type CreateTable struct {
// pos = Create
Expand All @@ -1658,10 +1659,24 @@ type CreateTable struct {
Columns []*ColumnDef
TableConstraints []*TableConstraint
PrimaryKeys []*IndexKey
Synonyms []*Synonym
Cluster *Cluster // optional
RowDeletionPolicy *CreateRowDeletionPolicy // optional
}

// Synonym is SYNONYM node in CREATE TABLE
//
// SYNONYM ({.Name | sql})
type Synonym struct {
// pos = Synonym
// end = Rparen + 1

Synonym token.Pos // position of "SYNONYM" pseudo keyword
Rparen token.Pos // position of ")"

Name *Ident
}

// CreateSequence is CREATE SEQUENCE statement node.
//
// CREATE SEQUENCE {{if .IfNotExists}}IF NOT EXISTS{{end}} {{.Name | sql}} }} {{.Options | sql}}
Expand Down
3 changes: 3 additions & 0 deletions ast/pos.go
Original file line number Diff line number Diff line change
Expand Up @@ -563,6 +563,9 @@ func (c *CreateTable) End() token.Pos {
return c.Rparen + 1
}

func (s *Synonym) Pos() token.Pos { return s.Synonym }
func (s *Synonym) End() token.Pos { return s.Rparen + 1 }

func (c *CreateSequence) Pos() token.Pos {
return c.Create
}
Expand Down
43 changes: 12 additions & 31 deletions ast/sql.go
Original file line number Diff line number Diff line change
Expand Up @@ -768,37 +768,18 @@ func (c *CreateDatabase) SQL() string {
}

func (c *CreateTable) SQL() string {
sql := "CREATE TABLE "
if c.IfNotExists {
sql += "IF NOT EXISTS "
}
sql += c.Name.SQL() + " ("
for i, c := range c.Columns {
if i != 0 {
sql += ", "
}
sql += c.SQL()
}
for _, c := range c.TableConstraints {
sql += ", " + c.SQL()
}
sql += ") "
sql += "PRIMARY KEY ("
for i, k := range c.PrimaryKeys {
if i != 0 {
sql += ", "
}
sql += k.SQL()
}
sql += ")"
if c.Cluster != nil {
sql += c.Cluster.SQL()
}
if c.RowDeletionPolicy != nil {
sql += c.RowDeletionPolicy.SQL()
}
return sql
}
return "CREATE TABLE " +
strOpt(c.IfNotExists, "IF NOT EXISTS ") +
c.Name.SQL() + " (" +
sqlJoin(c.Columns, ", ") + strOpt(len(c.Columns) > 0 && (len(c.TableConstraints) > 0 || len(c.Synonyms) > 0), ", ") +
sqlJoin(c.TableConstraints, ", ") + strOpt(len(c.TableConstraints) > 0 && len(c.Synonyms) > 0, ", ") +
sqlJoin(c.Synonyms, ", ") +
") PRIMARY KEY (" + sqlJoin(c.PrimaryKeys, ", ") + ")" +
sqlOpt("", c.Cluster, "") +
sqlOpt("", c.RowDeletionPolicy, "")
}

func (s *Synonym) SQL() string { return "SYNONYM (" + s.Name.SQL() + ")" }

func (c *CreateSequence) SQL() string {
sql := "CREATE SEQUENCE "
Expand Down
18 changes: 18 additions & 0 deletions parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -2232,6 +2232,7 @@ func (p *Parser) parseCreateTable(pos token.Pos) *ast.CreateTable {
p.expect("(")
var columns []*ast.ColumnDef
var constraints []*ast.TableConstraint
var synonyms []*ast.Synonym
for p.Token.Kind != token.TokenEOF {
if p.Token.Kind == ")" {
break
Expand All @@ -2251,6 +2252,9 @@ func (p *Parser) parseCreateTable(pos token.Pos) *ast.CreateTable {
ConstraintPos: token.InvalidPos,
Constraint: c,
})
case p.Token.IsKeywordLike("SYNONYM"):
synonym := p.parseSynonym()
synonyms = append(synonyms, synonym)
default:
columns = append(columns, p.parseColumnDef())
}
Expand Down Expand Up @@ -2288,6 +2292,7 @@ func (p *Parser) parseCreateTable(pos token.Pos) *ast.CreateTable {
Name: name,
Columns: columns,
TableConstraints: constraints,
Synonyms: synonyms,
PrimaryKeys: keys,
Cluster: cluster,
RowDeletionPolicy: rdp,
Expand Down Expand Up @@ -2432,6 +2437,19 @@ func (p *Parser) parseCheck() *ast.Check {
}
}

func (p *Parser) parseSynonym() *ast.Synonym {
pos := p.expectKeywordLike("SYNONYM").Pos
p.expect("(")
name := p.parseIdent()
rparen := p.expect(")").Pos

return &ast.Synonym{
Synonym: pos,
Rparen: rparen,
Name: name,
}
}

func (p *Parser) parseTypeNotNull() (t ast.SchemaType, notNull bool, null token.Pos) {
t = p.parseSchemaType()

Expand Down
5 changes: 5 additions & 0 deletions testdata/input/ddl/create_table_synonyms.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
CREATE TABLE Singers (
SingerId INT64 NOT NULL,
SingerName STRING(1024),
SYNONYM (Artists)
) PRIMARY KEY (SingerId)
7 changes: 7 additions & 0 deletions testdata/input/ddl/create_table_synonyms_abnormal.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
-- It is still valid CREATE TABLE statement.
CREATE TABLE Singers (
SYNONYM (Ignored),
SingerId INT64 NOT NULL,
SingerName STRING(1024),
SYNONYM (Artists)
) PRIMARY KEY (SingerId)
1 change: 1 addition & 0 deletions testdata/result/ddl/create_table.sql.txt
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,7 @@ create table foo (
Dir: "",
},
},
Synonyms: []*ast.Synonym(nil),
Cluster: (*ast.Cluster)(nil),
RowDeletionPolicy: (*ast.CreateRowDeletionPolicy)(nil),
}
Expand Down
1 change: 1 addition & 0 deletions testdata/result/ddl/create_table_cluster.sql.txt
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ create table foo (
},
TableConstraints: []*ast.TableConstraint(nil),
PrimaryKeys: []*ast.IndexKey(nil),
Synonyms: []*ast.Synonym(nil),
Cluster: &ast.Cluster{
Comma: 60,
OnDeleteEnd: -1,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ create table foo (
},
TableConstraints: []*ast.TableConstraint(nil),
PrimaryKeys: []*ast.IndexKey(nil),
Synonyms: []*ast.Synonym(nil),
Cluster: &ast.Cluster{
Comma: 78,
OnDeleteEnd: -1,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ create table foo (
Dir: "",
},
},
Cluster: &ast.Cluster{
Synonyms: []*ast.Synonym(nil),
Cluster: &ast.Cluster{
Comma: 51,
OnDeleteEnd: 115,
TableName: &ast.Ident{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ create table foo (
Dir: "",
},
},
Cluster: &ast.Cluster{
Synonyms: []*ast.Synonym(nil),
Cluster: &ast.Cluster{
Comma: 50,
OnDeleteEnd: 112,
TableName: &ast.Ident{
Expand Down
1 change: 1 addition & 0 deletions testdata/result/ddl/create_table_if_not_exists.sql.txt
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ create table if not exists foo (
Dir: "",
},
},
Synonyms: []*ast.Synonym(nil),
Cluster: (*ast.Cluster)(nil),
RowDeletionPolicy: (*ast.CreateRowDeletionPolicy)(nil),
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ create table foo (
},
TableConstraints: []*ast.TableConstraint(nil),
PrimaryKeys: []*ast.IndexKey(nil),
Synonyms: []*ast.Synonym(nil),
Cluster: (*ast.Cluster)(nil),
RowDeletionPolicy: &ast.CreateRowDeletionPolicy{
Comma: 78,
Expand Down
87 changes: 87 additions & 0 deletions testdata/result/ddl/create_table_synonyms.sql.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
--- create_table_synonyms.sql
CREATE TABLE Singers (
SingerId INT64 NOT NULL,
SingerName STRING(1024),
SYNONYM (Artists)
) PRIMARY KEY (SingerId)
--- AST
&ast.CreateTable{
Create: 0,
Rparen: 126,
IfNotExists: false,
Name: &ast.Ident{
NamePos: 13,
NameEnd: 20,
Name: "Singers",
},
Columns: []*ast.ColumnDef{
&ast.ColumnDef{
Null: 46,
Name: &ast.Ident{
NamePos: 27,
NameEnd: 35,
Name: "SingerId",
},
Type: &ast.ScalarSchemaType{
NamePos: 36,
Name: "INT64",
},
NotNull: true,
DefaultExpr: (*ast.ColumnDefaultExpr)(nil),
GeneratedExpr: (*ast.GeneratedColumnExpr)(nil),
Options: (*ast.Options)(nil),
},
&ast.ColumnDef{
Null: -1,
Name: &ast.Ident{
NamePos: 56,
NameEnd: 66,
Name: "SingerName",
},
Type: &ast.SizedSchemaType{
NamePos: 67,
Rparen: 78,
Name: "STRING",
Max: false,
Size: &ast.IntLiteral{
ValuePos: 74,
ValueEnd: 78,
Base: 10,
Value: "1024",
},
},
NotNull: false,
DefaultExpr: (*ast.ColumnDefaultExpr)(nil),
GeneratedExpr: (*ast.GeneratedColumnExpr)(nil),
Options: (*ast.Options)(nil),
},
},
TableConstraints: []*ast.TableConstraint(nil),
PrimaryKeys: []*ast.IndexKey{
&ast.IndexKey{
DirPos: -1,
Name: &ast.Ident{
NamePos: 118,
NameEnd: 126,
Name: "SingerId",
},
Dir: "",
},
},
Synonyms: []*ast.Synonym{
&ast.Synonym{
Synonym: 85,
Rparen: 101,
Name: &ast.Ident{
NamePos: 94,
NameEnd: 101,
Name: "Artists",
},
},
},
Cluster: (*ast.Cluster)(nil),
RowDeletionPolicy: (*ast.CreateRowDeletionPolicy)(nil),
}

--- SQL
CREATE TABLE Singers (SingerId INT64 NOT NULL, SingerName STRING(1024), SYNONYM (Artists)) PRIMARY KEY (SingerId)
Loading

0 comments on commit ff8609d

Please sign in to comment.