Repository: cloudspannerecosystem/memefish (go)

Issue:
Support ALTER DATABASE statement
`ALTER DATABASE` statement is not yet supported.

https://cloud.google.com/spanner/docs/reference/standard-sql/data-definition-language#alter-database

```
ALTER DATABASE database_id
    action

where database_id is:
    {a—z}[{a—z|0—9|_|-}+]{a—z|0—9}

and action is:
    SET OPTIONS ( options_def [, ... ] )

and options_def is:
    { default_leader = { 'region' | null } |
      optimizer_version = { 1 ... 7 | null } |
      optimizer_statistics_package = { 'package_name' | null } |
      version_retention_period = { 'duration' | null } }
```

Related

- #115 

Pull request description:
Implement ALTER DATABASE statement
This PR implements `ALTER DATABASE` statement.

fix #131 

Patch:
diff --git a/ast/ast.go b/ast/ast.go
index 46cc325..865f123 100644
--- a/ast/ast.go
+++ b/ast/ast.go
@@ -57,6 +57,7 @@ type Statement interface {
 
 func (QueryStatement) isStatement()     {}
 func (CreateDatabase) isStatement()     {}
+func (AlterDatabase) isStatement()      {}
 func (CreateTable) isStatement()        {}
 func (AlterTable) isStatement()         {}
 func (DropTable) isStatement()          {}
@@ -265,6 +266,7 @@ type DDL interface {
 // - https://cloud.google.com/spanner/docs/reference/standard-sql/data-definition-language
 
 func (CreateDatabase) isDDL()     {}
+func (AlterDatabase) isDDL()      {}
 func (CreateTable) isDDL()        {}
 func (AlterTable) isDDL()         {}
 func (DropTable) isDDL()          {}
@@ -1636,6 +1638,19 @@ type CreateDatabase struct {
 	Name *Ident
 }
 
+// AlterDatabase is ALTER DATABASE statement node.
+//
+//	ALTER DATABASE {{.Name | sql}} SET {{.Options | sql}}
+type AlterDatabase struct {
+	// pos = Alter
+	// end = Name.end
+
+	Alter token.Pos // position of "ALTER" keyword
+
+	Name    *Ident
+	Options *Options
+}
+
 // CreateTable is CREATE TABLE statement node.
 //
 //	CREATE TABLE {{if .IfNotExists}}IF NOT EXISTS{{end}} {{.Name | sql}} (
diff --git a/ast/pos.go b/ast/pos.go
index a64d7eb..54e18fa 100644
--- a/ast/pos.go
+++ b/ast/pos.go
@@ -549,6 +549,9 @@ func (g *OptionsDef) End() token.Pos { return g.Value.End() }
 func (c *CreateDatabase) Pos() token.Pos { return c.Create }
 func (c *CreateDatabase) End() token.Pos { return c.Name.End() }
 
+func (d *AlterDatabase) Pos() token.Pos { return d.Alter }
+func (d *AlterDatabase) End() token.Pos { return d.Options.End() }
+
 func (c *CreateTable) Pos() token.Pos {
 	return c.Create
 }
diff --git a/ast/sql.go b/ast/sql.go
index 6247186..7535bcb 100644
--- a/ast/sql.go
+++ b/ast/sql.go
@@ -767,6 +767,10 @@ func (c *CreateDatabase) SQL() string {
 	return "CREATE DATABASE " + c.Name.SQL()
 }
 
+func (d *AlterDatabase) SQL() string {
+	return "ALTER DATABASE " + d.Name.SQL() + " SET " + d.Options.SQL()
+}
+
 func (c *CreateTable) SQL() string {
 	return "CREATE TABLE " +
 		strOpt(c.IfNotExists, "IF NOT EXISTS ") +
diff --git a/parser.go b/parser.go
index 419b967..ced5fb7 100644
--- a/parser.go
+++ b/parser.go
@@ -2171,6 +2171,8 @@ func (p *Parser) parseDDL() ast.DDL {
 		switch {
 		case p.Token.IsKeywordLike("TABLE"):
 			return p.parseAlterTable(pos)
+		case p.Token.IsKeywordLike("DATABASE"):
+			return p.parseAlterDatabase(pos)
 		case p.Token.IsKeywordLike("INDEX"):
 			return p.parseAlterIndex(pos)
 		case p.Token.IsKeywordLike("SEQUENCE"):
@@ -2222,6 +2224,19 @@ func (p *Parser) parseCreateDatabase(pos token.Pos) *ast.CreateDatabase {
 	}
 }
 
+func (p *Parser) parseAlterDatabase(pos token.Pos) *ast.AlterDatabase {
+	p.expectKeywordLike("DATABASE")
+	name := p.parseIdent()
+	p.expect("SET")
+	options := p.parseOptions()
+
+	return &ast.AlterDatabase{
+		Alter:   pos,
+		Name:    name,
+		Options: options,
+	}
+}
+
 func (p *Parser) parseCreateTable(pos token.Pos) *ast.CreateTable {
 	p.expectKeywordLike("TABLE")
 	ifNotExists := p.parseIfNotExists()


Test patch:
diff --git a/ast/ast_test.go b/ast/ast_test.go
index 3232f53..1a5d532 100644
--- a/ast/ast_test.go
+++ b/ast/ast_test.go
@@ -7,6 +7,7 @@ import (
 func TestStatement(t *testing.T) {
 	Statement(&QueryStatement{}).isStatement()
 	Statement(&CreateDatabase{}).isStatement()
+	Statement(&AlterDatabase{}).isStatement()
 	Statement(&CreateTable{}).isStatement()
 	Statement(&AlterTable{}).isStatement()
 	Statement(&DropTable{}).isStatement()
@@ -136,6 +137,7 @@ func TestStringValue(t *testing.T) {
 
 func TestDDL(t *testing.T) {
 	DDL(&CreateDatabase{}).isDDL()
+  DDL(&AlterDatabase{}).isDDL()
 	DDL(&CreateTable{}).isDDL()
 	DDL(&AlterTable{}).isDDL()
 	DDL(&DropTable{}).isDDL()
diff --git a/testdata/input/ddl/alter_database.sql b/testdata/input/ddl/alter_database.sql
new file mode 100644
index 0000000..94ed793
--- /dev/null
+++ b/testdata/input/ddl/alter_database.sql
@@ -0,0 +1,7 @@
+ALTER DATABASE dbname SET OPTIONS (
+    optimizer_version=2,
+    optimizer_statistics_package='auto_20191128_14_47_22UTC',
+    version_retention_period='7d',
+    enable_key_visualizer=true,
+    default_leader='europe-west1'
+  )
\ No newline at end of file
diff --git a/testdata/result/ddl/alter_database.sql.txt b/testdata/result/ddl/alter_database.sql.txt
new file mode 100644
index 0000000..3fc122a
--- /dev/null
+++ b/testdata/result/ddl/alter_database.sql.txt
@@ -0,0 +1,86 @@
+--- alter_database.sql
+ALTER DATABASE dbname SET OPTIONS (
+    optimizer_version=2,
+    optimizer_statistics_package='auto_20191128_14_47_22UTC',
+    version_retention_period='7d',
+    enable_key_visualizer=true,
+    default_leader='europe-west1'
+  )
+--- AST
+&ast.AlterDatabase{
+  Alter: 0,
+  Name:  &ast.Ident{
+    NamePos: 15,
+    NameEnd: 21,
+    Name:    "dbname",
+  },
+  Options: &ast.Options{
+    Options: 26,
+    Rparen:  226,
+    Records: []*ast.OptionsDef{
+      &ast.OptionsDef{
+        Name: &ast.Ident{
+          NamePos: 40,
+          NameEnd: 57,
+          Name:    "optimizer_version",
+        },
+        Value: &ast.IntLiteral{
+          ValuePos: 58,
+          ValueEnd: 59,
+          Base:     10,
+          Value:    "2",
+        },
+      },
+      &ast.OptionsDef{
+        Name: &ast.Ident{
+          NamePos: 65,
+          NameEnd: 93,
+          Name:    "optimizer_statistics_package",
+        },
+        Value: &ast.StringLiteral{
+          ValuePos: 94,
+          ValueEnd: 121,
+          Value:    "auto_20191128_14_47_22UTC",
+        },
+      },
+      &ast.OptionsDef{
+        Name: &ast.Ident{
+          NamePos: 127,
+          NameEnd: 151,
+          Name:    "version_retention_period",
+        },
+        Value: &ast.StringLiteral{
+          ValuePos: 152,
+          ValueEnd: 156,
+          Value:    "7d",
+        },
+      },
+      &ast.OptionsDef{
+        Name: &ast.Ident{
+          NamePos: 162,
+          NameEnd: 183,
+          Name:    "enable_key_visualizer",
+        },
+        Value: &ast.BoolLiteral{
+          ValuePos: 184,
+          Value:    true,
+        },
+      },
+      &ast.OptionsDef{
+        Name: &ast.Ident{
+          NamePos: 194,
+          NameEnd: 208,
+          Name:    "default_leader",
+        },
+        Value: &ast.StringLiteral{
+          ValuePos: 209,
+          ValueEnd: 223,
+          Value:    "europe-west1",
+        },
+      },
+    },
+  },
+}
+
+--- SQL
+ALTER DATABASE dbname SET OPTIONS (optimizer_version = 2, optimizer_statistics_package = "auto_20191128_14_47_22UTC", version_retention_period = "7d", enable_key_visualizer = true, default_leader = "europe-west1")
diff --git a/testdata/result/statement/alter_database.sql.txt b/testdata/result/statement/alter_database.sql.txt
new file mode 100644
index 0000000..3fc122a
--- /dev/null
+++ b/testdata/result/statement/alter_database.sql.txt
@@ -0,0 +1,86 @@
+--- alter_database.sql
+ALTER DATABASE dbname SET OPTIONS (
+    optimizer_version=2,
+    optimizer_statistics_package='auto_20191128_14_47_22UTC',
+    version_retention_period='7d',
+    enable_key_visualizer=true,
+    default_leader='europe-west1'
+  )
+--- AST
+&ast.AlterDatabase{
+  Alter: 0,
+  Name:  &ast.Ident{
+    NamePos: 15,
+    NameEnd: 21,
+    Name:    "dbname",
+  },
+  Options: &ast.Options{
+    Options: 26,
+    Rparen:  226,
+    Records: []*ast.OptionsDef{
+      &ast.OptionsDef{
+        Name: &ast.Ident{
+          NamePos: 40,
+          NameEnd: 57,
+          Name:    "optimizer_version",
+        },
+        Value: &ast.IntLiteral{
+          ValuePos: 58,
+          ValueEnd: 59,
+          Base:     10,
+          Value:    "2",
+        },
+      },
+      &ast.OptionsDef{
+        Name: &ast.Ident{
+          NamePos: 65,
+          NameEnd: 93,
+          Name:    "optimizer_statistics_package",
+        },
+        Value: &ast.StringLiteral{
+          ValuePos: 94,
+          ValueEnd: 121,
+          Value:    "auto_20191128_14_47_22UTC",
+        },
+      },
+      &ast.OptionsDef{
+        Name: &ast.Ident{
+          NamePos: 127,
+          NameEnd: 151,
+          Name:    "version_retention_period",
+        },
+        Value: &ast.StringLiteral{
+          ValuePos: 152,
+          ValueEnd: 156,
+          Value:    "7d",
+        },
+      },
+      &ast.OptionsDef{
+        Name: &ast.Ident{
+          NamePos: 162,
+          NameEnd: 183,
+          Name:    "enable_key_visualizer",
+        },
+        Value: &ast.BoolLiteral{
+          ValuePos: 184,
+          Value:    true,
+        },
+      },
+      &ast.OptionsDef{
+        Name: &ast.Ident{
+          NamePos: 194,
+          NameEnd: 208,
+          Name:    "default_leader",
+        },
+        Value: &ast.StringLiteral{
+          ValuePos: 209,
+          ValueEnd: 223,
+          Value:    "europe-west1",
+        },
+      },
+    },
+  },
+}
+
+--- SQL
+ALTER DATABASE dbname SET OPTIONS (optimizer_version = 2, optimizer_statistics_package = "auto_20191128_14_47_22UTC", version_retention_period = "7d", enable_key_visualizer = true, default_leader = "europe-west1")