spanner/spansql: parse STORING and INTERLEAVE IN clauses in CREATE INDEX

Change-Id: If71a6f1e318d525601d5670697b1387a48ce7b00
Reviewed-on: https://code-review.googlesource.com/c/gocloud/+/43850
Reviewed-by: kokoro <noreply+kokoro@google.com>
Reviewed-by: Knut Olav Løite <koloite@gmail.com>
diff --git a/spanner/spannertest/README.md b/spanner/spannertest/README.md
index 89b7ea2..578e9e9 100644
--- a/spanner/spannertest/README.md
+++ b/spanner/spannertest/README.md
@@ -32,7 +32,6 @@
 - subselects
 - set operations (UNION, INTERSECT, EXCEPT)
 - SELECT star expressions
-- STORING and INTERLEAVE IN clauses for CREATE INDEX
 - partition support
 - conditional expressions
 - table sampling (implementation)
diff --git a/spanner/spansql/parser.go b/spanner/spansql/parser.go
index af5446f..4388508 100644
--- a/spanner/spansql/parser.go
+++ b/spanner/spansql/parser.go
@@ -665,6 +665,12 @@
 
 		index_name:
 			{a—z|A—Z}[{a—z|A—Z|0—9|_}+]
+
+		storing_clause:
+			STORING ( column_name [, ...] )
+
+		interleave_clause:
+			INTERLEAVE IN table_name
 	*/
 
 	var unique, nullFiltered bool
@@ -705,6 +711,25 @@
 	if err != nil {
 		return CreateIndex{}, err
 	}
+
+	if p.sniff("STORING") {
+		p.expect("STORING")
+		ci.Storing, err = p.parseColumnNameList()
+		if err != nil {
+			return CreateIndex{}, err
+		}
+	}
+
+	if p.sniff(",", "INTERLEAVE", "IN") {
+		p.expect(",")
+		p.expect("INTERLEAVE")
+		p.expect("IN")
+		ci.Interleave, err = p.parseTableOrIndexOrColumnName()
+		if err != nil {
+			return CreateIndex{}, err
+		}
+	}
+
 	return ci, nil
 }
 
@@ -878,6 +903,41 @@
 	return kp, nil
 }
 
+func (p *parser) parseColumnNameList() ([]string, error) {
+	// TODO: This is very similar to parseKeyPartList and parseExprList. Refactor.
+
+	if err := p.expect("("); err != nil {
+		return nil, err
+	}
+	var list []string
+	for {
+		if err := p.expect(")"); err == nil {
+			break
+		}
+		p.back()
+
+		n, err := p.parseTableOrIndexOrColumnName()
+		if err != nil {
+			return nil, err
+		}
+		list = append(list, n)
+
+		// ")" or "," should be next.
+		tok := p.next()
+		if tok.err != nil {
+			return nil, err
+		}
+		if tok.value == ")" {
+			break
+		} else if tok.value == "," {
+			continue
+		} else {
+			return nil, p.errorf(`got %q, want ")" or ","`, tok.value)
+		}
+	}
+	return list, nil
+}
+
 var baseTypes = map[string]TypeBase{
 	"BOOL":      Bool,
 	"INT64":     Int64,
diff --git a/spanner/spansql/parser_test.go b/spanner/spansql/parser_test.go
index 3e92183..ec12f44 100644
--- a/spanner/spansql/parser_test.go
+++ b/spanner/spansql/parser_test.go
@@ -157,7 +157,7 @@
 		) PRIMARY KEY(System, RepoPath);
 		CREATE UNIQUE INDEX MyFirstIndex ON FooBar (
 			Count DESC
-		);
+		) STORING (Count), INTERLEAVE IN SomeTable;
 		CREATE TABLE FooBarAux (
 			System STRING(MAX) NOT NULL,
 			RepoPath STRING(MAX) NOT NULL,
@@ -191,10 +191,12 @@
 				},
 			},
 			CreateIndex{
-				Name:    "MyFirstIndex",
-				Table:   "FooBar",
-				Columns: []KeyPart{{Column: "Count", Desc: true}},
-				Unique:  true,
+				Name:       "MyFirstIndex",
+				Table:      "FooBar",
+				Columns:    []KeyPart{{Column: "Count", Desc: true}},
+				Unique:     true,
+				Storing:    []string{"Count"},
+				Interleave: "SomeTable",
 			},
 			CreateTable{
 				Name: "FooBarAux",
diff --git a/spanner/spansql/sql.go b/spanner/spansql/sql.go
index 5a3b184..cbf28fd 100644
--- a/spanner/spansql/sql.go
+++ b/spanner/spansql/sql.go
@@ -20,6 +20,7 @@
 // as the SQL dialect that this package parses.
 
 import "strconv"
+import "strings"
 
 func (ct CreateTable) SQL() string {
 	str := "CREATE TABLE " + ct.Name + " (\n"
@@ -56,6 +57,14 @@
 		str += c.SQL()
 	}
 	str += ")"
+	if len(ci.Storing) > 0 {
+		str += " STORING ("
+		str += strings.Join(ci.Storing, ", ")
+		str += ")"
+	}
+	if ci.Interleave != "" {
+		str += ", INTERLEAVE IN " + ci.Interleave
+	}
 	return str
 }
 
diff --git a/spanner/spansql/types.go b/spanner/spansql/types.go
index 9b5cefe..542b605 100644
--- a/spanner/spansql/types.go
+++ b/spanner/spansql/types.go
@@ -47,7 +47,8 @@
 	Unique       bool
 	NullFiltered bool
 
-	// TODO: storing_clause, interleave_clause
+	Storing    []string
+	Interleave string
 }
 
 // DropTable represents a DROP TABLE statement.