spanner/spansql: fix parsing of table constraints
"CONSTRAINT" and friends are not reserved keywords (verified
internally), and the parsing ambiguity resulting from this must be
resolved with token lookahead.
Change-Id: Ie321319158536d8f0f346a5717fdbca9b407e921
Reviewed-on: https://code-review.googlesource.com/c/gocloud/+/53091
Reviewed-by: kokoro <noreply+kokoro@google.com>
Reviewed-by: Knut Olav Løite <koloite@gmail.com>
diff --git a/spanner/spansql/keywords.go b/spanner/spansql/keywords.go
index 4dce1ca..e7a8941 100644
--- a/spanner/spansql/keywords.go
+++ b/spanner/spansql/keywords.go
@@ -123,12 +123,6 @@
"WINDOW": true,
"WITH": true,
"WITHIN": true,
-
- // For new foreign key support; these aren't officially listed yet.
- "CONSTRAINT": true,
- "FOREIGN": true,
- "KEY": true,
- "REFERENCES": true,
}
// funcs is the set of reserved keywords that are functions.
diff --git a/spanner/spansql/parser.go b/spanner/spansql/parser.go
index e1ec959..308073f 100644
--- a/spanner/spansql/parser.go
+++ b/spanner/spansql/parser.go
@@ -971,7 +971,7 @@
ct := &CreateTable{Name: tname, Position: pos}
err = p.parseCommaList(func(p *parser) *parseError {
- if p.sniff("CONSTRAINT") || p.sniff("FOREIGN") {
+ if p.sniffTableConstraint() {
tc, err := p.parseTableConstraint()
if err != nil {
return err
@@ -1030,6 +1030,34 @@
return ct, nil
}
+func (p *parser) sniffTableConstraint() bool {
+ // Unfortunately the Cloud Spanner grammar is LL(3) because
+ // CONSTRAINT BOOL
+ // could be the start of a declaration of a column called "CONSTRAINT" of boolean type,
+ // or it could be the start of a foreign key constraint called "BOOL".
+ // We have to sniff up to the third token to see what production it is.
+ // If we have "FOREIGN" and "KEY", this is an unnamed table constraint.
+ // If we have "CONSTRAINT", an identifier and "FOREIGN", this is a table constraint.
+ // Otherwise, this is a column definition.
+
+ if p.sniff("FOREIGN", "KEY") {
+ return true
+ }
+
+ // Store parser state, and peek ahead.
+ // Restore on the way out.
+ orig := *p
+ defer func() { *p = orig }()
+
+ if !p.eat("CONSTRAINT") {
+ return false
+ }
+ if _, err := p.parseTableOrIndexOrColumnName(); err != nil {
+ return false
+ }
+ return p.eat("FOREIGN")
+}
+
func (p *parser) parseCreateIndex() (*CreateIndex, *parseError) {
debugf("parseCreateIndex: %v", p)
diff --git a/spanner/spansql/parser_test.go b/spanner/spansql/parser_test.go
index 2c3d690..af3be98 100644
--- a/spanner/spansql/parser_test.go
+++ b/spanner/spansql/parser_test.go
@@ -253,6 +253,7 @@
RepoPath STRING(MAX) NOT NULL,
FOREIGN KEY (System, RepoPath) REFERENCES Stranger (Sys, RPath), -- unnamed foreign key
Author STRING(MAX) NOT NULL,
+ CONSTRAINT BOOL, -- not a constraint
) PRIMARY KEY(System, RepoPath, Author),
INTERLEAVE IN PARENT FooBar ON DELETE CASCADE;
@@ -303,6 +304,7 @@
{Name: "System", Type: Type{Base: String, Len: MaxLen}, NotNull: true, Position: line(12)},
{Name: "RepoPath", Type: Type{Base: String, Len: MaxLen}, NotNull: true, Position: line(14)},
{Name: "Author", Type: Type{Base: String, Len: MaxLen}, NotNull: true, Position: line(16)},
+ {Name: "CONSTRAINT", Type: Type{Base: Bool}, Position: line(17)},
},
Constraints: []TableConstraint{
{
@@ -338,13 +340,13 @@
},
&AlterTable{
Name: "FooBar",
- Alteration: AddColumn{Def: ColumnDef{Name: "TZ", Type: Type{Base: Bytes, Len: 20}, Position: line(20)}},
- Position: line(20),
+ Alteration: AddColumn{Def: ColumnDef{Name: "TZ", Type: Type{Base: Bytes, Len: 20}, Position: line(21)}},
+ Position: line(21),
},
&AlterTable{
Name: "FooBar",
Alteration: DropColumn{Name: "TZ"},
- Position: line(21),
+ Position: line(22),
},
&AlterTable{
Name: "FooBar",
@@ -354,21 +356,21 @@
Columns: []string{"RepoPath"},
RefTable: "Repos",
RefColumns: []string{"RPath"},
- Position: line(22),
+ Position: line(23),
},
- Position: line(22),
+ Position: line(23),
}},
- Position: line(22),
+ Position: line(23),
},
&AlterTable{
Name: "FooBar",
Alteration: DropConstraint{Name: "Con3"},
- Position: line(23),
+ Position: line(24),
},
&AlterTable{
Name: "FooBar",
Alteration: SetOnDelete{Action: NoActionOnDelete},
- Position: line(24),
+ Position: line(25),
},
&AlterTable{
Name: "FooBar",
@@ -376,21 +378,21 @@
Name: "Author",
Type: Type{Base: String, Len: MaxLen},
NotNull: true,
- Position: line(25),
+ Position: line(26),
}},
- Position: line(25),
+ Position: line(26),
},
- &DropIndex{Name: "MyFirstIndex", Position: line(27)},
- &DropTable{Name: "FooBar", Position: line(28)},
+ &DropIndex{Name: "MyFirstIndex", Position: line(28)},
+ &DropTable{Name: "FooBar", Position: line(29)},
&CreateTable{
Name: "NonScalars",
Columns: []ColumnDef{
- {Name: "Dummy", Type: Type{Base: Int64}, NotNull: true, Position: line(33)},
- {Name: "Ids", Type: Type{Array: true, Base: Int64}, Position: line(34)},
- {Name: "Names", Type: Type{Array: true, Base: String, Len: MaxLen}, Position: line(35)},
+ {Name: "Dummy", Type: Type{Base: Int64}, NotNull: true, Position: line(34)},
+ {Name: "Ids", Type: Type{Array: true, Base: Int64}, Position: line(35)},
+ {Name: "Names", Type: Type{Array: true, Base: String, Len: MaxLen}, Position: line(36)},
},
PrimaryKey: []KeyPart{{Column: "Dummy"}},
- Position: line(32),
+ Position: line(33),
},
}, Comments: []*Comment{
{Marker: "#", Start: line(2), End: line(2),
@@ -401,11 +403,13 @@
Text: []string{" This is a", "\t\t\t\t\t\t * multiline comment."}},
{Marker: "--", Start: line(15), End: line(15),
Text: []string{"unnamed foreign key"}},
- {Marker: "--", Isolated: true, Start: line(30), End: line(31),
+ {Marker: "--", Start: line(17), End: line(17),
+ Text: []string{"not a constraint"}},
+ {Marker: "--", Isolated: true, Start: line(31), End: line(32),
Text: []string{"This table has some commentary", "that spans multiple lines."}},
// These comments shouldn't get combined:
- {Marker: "--", Start: line(33), End: line(33), Text: []string{"dummy comment"}},
- {Marker: "--", Start: line(34), End: line(34), Text: []string{"comment on ids"}},
+ {Marker: "--", Start: line(34), End: line(34), Text: []string{"dummy comment"}},
+ {Marker: "--", Start: line(35), End: line(35), Text: []string{"comment on ids"}},
}}},
// No trailing comma:
{`ALTER TABLE T ADD COLUMN C2 INT64`, &DDL{Filename: "filename", List: []DDLStmt{