spanner/spansql: parse OPTIONS of column definition in DDL. Fixes #1693

Add parse `option_def` such as `OPTIONS (allow_commit_timestamp = {true | null})`.
ref. cloud.google.com/spanner/docs/data-definition-language

Change-Id: Icefe76d3da11842f76ea983afc349bf6fc87cecd
Reviewed-on: https://code-review.googlesource.com/c/gocloud/+/46390
Reviewed-by: kokoro <noreply+kokoro@google.com>
Reviewed-by: David Symonds <dsymonds@golang.org>
diff --git a/spanner/spansql/parser.go b/spanner/spansql/parser.go
index 75a3381..801770b 100644
--- a/spanner/spansql/parser.go
+++ b/spanner/spansql/parser.go
@@ -1021,20 +1021,54 @@
 		return ColumnDef{}, err
 	}
 
-	tok := p.next()
-	if tok.err != nil || tok.value != "NOT" {
-		// End of the column_def.
-		p.back()
-		return cd, nil
+	if p.eat("NOT", "NULL") {
+		cd.NotNull = true
 	}
-	if err := p.expect("NULL"); err != nil {
-		return ColumnDef{}, err
+
+	if p.eat("OPTIONS") {
+		if cd.AllowCommitTimestamp, err = p.parseColumnOptions(); err != nil {
+			return ColumnDef{}, err
+		}
 	}
-	cd.NotNull = true
 
 	return cd, nil
 }
 
+// parseColumnOptions returns allow_commit_timestamp.
+func (p *parser) parseColumnOptions() (allowCommitTimestamp *bool, err error) {
+	debugf("parseColumnOptions: %v", p)
+	/*
+		options_def:
+			OPTIONS (allow_commit_timestamp = { true | null })
+	*/
+
+	if err = p.expect("("); err != nil {
+		return nil, err
+	}
+
+	if p.eat("allow_commit_timestamp", "=") {
+		tok := p.next()
+		if tok.err != nil {
+			return nil, tok.err
+		}
+		allowCommitTimestamp = new(bool)
+		switch tok.value {
+		case "true":
+			*allowCommitTimestamp = true
+		case "null":
+			*allowCommitTimestamp = false
+		default:
+			return nil, p.errorf("got %q, want true or null", tok.value)
+		}
+	}
+
+	if err := p.expect(")"); err != nil {
+		return nil, err
+	}
+
+	return
+}
+
 func (p *parser) parseKeyPartList() ([]KeyPart, error) {
 	var list []KeyPart
 	err := p.parseCommaList(func(p *parser) error {
diff --git a/spanner/spansql/parser_test.go b/spanner/spansql/parser_test.go
index 078e319..777f929 100644
--- a/spanner/spansql/parser_test.go
+++ b/spanner/spansql/parser_test.go
@@ -219,7 +219,8 @@
 			System STRING(MAX) NOT NULL,  # This is a comment.
 			RepoPath STRING(MAX) NOT NULL,  -- This is another comment.
 			Count INT64, /* This is a
-			              * multiline comment. */
+						  * multiline comment. */
+			UpdatedAt TIMESTAMP OPTIONS (allow_commit_timestamp = true),
 		) PRIMARY KEY(System, RepoPath);
 		CREATE UNIQUE INDEX MyFirstIndex ON FooBar (
 			Count DESC
@@ -250,6 +251,7 @@
 					{Name: "System", Type: Type{Base: String, Len: MaxLen}, NotNull: true},
 					{Name: "RepoPath", Type: Type{Base: String, Len: MaxLen}, NotNull: true},
 					{Name: "Count", Type: Type{Base: Int64}},
+					{Name: "UpdatedAt", Type: Type{Base: Timestamp}, AllowCommitTimestamp: boolAddr(true)},
 				},
 				PrimaryKey: []KeyPart{
 					{Column: "System"},
diff --git a/spanner/spansql/sql.go b/spanner/spansql/sql.go
index a9d9072..5390c4a 100644
--- a/spanner/spansql/sql.go
+++ b/spanner/spansql/sql.go
@@ -111,6 +111,13 @@
 	if cd.NotNull {
 		str += " NOT NULL"
 	}
+	if cd.Type.Base == Timestamp && cd.AllowCommitTimestamp != nil {
+		if *cd.AllowCommitTimestamp {
+			str += " OPTIONS (allow_commit_timestamp = true)"
+		} else {
+			str += " OPTIONS (allow_commit_timestamp = null)"
+		}
+	}
 	return str
 }
 
diff --git a/spanner/spansql/sql_test.go b/spanner/spansql/sql_test.go
index 46d4c99..394150c 100644
--- a/spanner/spansql/sql_test.go
+++ b/spanner/spansql/sql_test.go
@@ -21,6 +21,10 @@
 	"testing"
 )
 
+func boolAddr(b bool) *bool {
+	return &b
+}
+
 func TestSQL(t *testing.T) {
 	reparseDDL := func(s string) (interface{}, error) {
 		ddl, err := ParseDDLStmt(s)
@@ -52,9 +56,10 @@
 					{Name: "Cf", Type: Type{Base: Bytes, Len: 4711}},
 					{Name: "Cg", Type: Type{Base: Bytes, Len: MaxLen}},
 					{Name: "Ch", Type: Type{Base: Date}},
-					{Name: "Ci", Type: Type{Base: Timestamp}},
+					{Name: "Ci", Type: Type{Base: Timestamp}, AllowCommitTimestamp: boolAddr(true)},
 					{Name: "Cj", Type: Type{Array: true, Base: Int64}},
 					{Name: "Ck", Type: Type{Array: true, Base: String, Len: MaxLen}},
+					{Name: "Cl", Type: Type{Base: Timestamp}, AllowCommitTimestamp: boolAddr(false)},
 				},
 				PrimaryKey: []KeyPart{
 					{Column: "Ca"},
@@ -70,9 +75,10 @@
   Cf BYTES(4711),
   Cg BYTES(MAX),
   Ch DATE,
-  Ci TIMESTAMP,
+  Ci TIMESTAMP OPTIONS (allow_commit_timestamp = true),
   Cj ARRAY<INT64>,
   Ck ARRAY<STRING(MAX)>,
+  Cl TIMESTAMP OPTIONS (allow_commit_timestamp = null),
 ) PRIMARY KEY(Ca, Cb DESC)`,
 			reparseDDL,
 		},
diff --git a/spanner/spansql/types.go b/spanner/spansql/types.go
index ff7da97..21ff143 100644
--- a/spanner/spansql/types.go
+++ b/spanner/spansql/types.go
@@ -100,6 +100,12 @@
 	Name    string
 	Type    Type
 	NotNull bool
+
+	// AllowCommitTimestamp represents a column OPTIONS.
+	// `true` if query is `OPTIONS (allow_commit_timestamp = true)`
+	// `false` if query is `OPTIONS (allow_commit_timestamp = null)`
+	// `nil` if there are no OPTIONS
+	AllowCommitTimestamp *bool
 }
 
 // Type represents a column type.