Repository: cloudspannerecosystem/memefish (go)

Issue:
Support CREATE PLACEMENT statement
https://cloud.google.com/spanner/docs/reference/standard-sql/data-definition-language#create-placement

```
CREATE PLACEMENT `placement_name` [ partition_def ]

where partition_def is:
    { OPTIONS ( instance_partition="partition_id" [, default_leader="leader_region_id" ] ) }
```

Pull request description:
Implement CREATE PLACEMENT statement
This PR implements `CREATE PLACEMENT` DDL statement.

resolve #165 

Patch:
diff --git a/ast/ast.go b/ast/ast.go
index 6956f65..3c6fd06 100644
--- a/ast/ast.go
+++ b/ast/ast.go
@@ -64,6 +64,7 @@ func (CreateSchema) isStatement()       {}
 func (DropSchema) isStatement()         {}
 func (CreateDatabase) isStatement()     {}
 func (AlterDatabase) isStatement()      {}
+func (CreatePlacement) isStatement()    {}
 func (CreateProtoBundle) isStatement()  {}
 func (AlterProtoBundle) isStatement()   {}
 func (DropProtoBundle) isStatement()    {}
@@ -307,6 +308,7 @@ func (CreateSchema) isDDL()       {}
 func (DropSchema) isDDL()         {}
 func (CreateDatabase) isDDL()     {}
 func (AlterDatabase) isDDL()      {}
+func (CreatePlacement) isDDL()    {}
 func (CreateProtoBundle) isDDL()  {}
 func (AlterProtoBundle) isDDL()   {}
 func (DropProtoBundle) isDDL()    {}
@@ -1827,6 +1829,19 @@ type AlterDatabase struct {
 	Options *Options
 }
 
+// CreatePlacement is CREATE PLACEMENT statement node.
+//
+//	CREATE PLACEMENT {{.Name | sql}} {{.Options | sqlOpt}}
+type CreatePlacement struct {
+	// pos = Create
+	// end = (Options ?? Name).end
+
+	Create token.Pos // position of "CREATE" keyword
+
+	Name    *Ident
+	Options *Options // optional
+}
+
 // ================================================================================
 //
 // PROTO BUNDLE statements
diff --git a/ast/pos.go b/ast/pos.go
index af1b319..7b6dcf1 100644
--- a/ast/pos.go
+++ b/ast/pos.go
@@ -822,6 +822,14 @@ func (a *AlterDatabase) End() token.Pos {
 	return nodeEnd(wrapNode(a.Name))
 }
 
+func (c *CreatePlacement) Pos() token.Pos {
+	return c.Create
+}
+
+func (c *CreatePlacement) End() token.Pos {
+	return nodeEnd(nodeChoice(wrapNode(c.Options), wrapNode(c.Name)))
+}
+
 func (p *ProtoBundleTypes) Pos() token.Pos {
 	return p.Lparen
 }
diff --git a/ast/sql.go b/ast/sql.go
index 242b653..01c2098 100644
--- a/ast/sql.go
+++ b/ast/sql.go
@@ -794,6 +794,10 @@ func (d *AlterDatabase) SQL() string {
 	return "ALTER DATABASE " + d.Name.SQL() + " SET " + d.Options.SQL()
 }
 
+func (c *CreatePlacement) SQL() string {
+	return "CREATE PLACEMENT " + c.Name.SQL() + sqlOpt(" ", c.Options, " ")
+}
+
 func (p *ProtoBundleTypes) SQL() string { return "(" + sqlJoin(p.Types, ", ") + ")" }
 
 func (b *CreateProtoBundle) SQL() string { return "CREATE PROTO BUNDLE " + b.Types.SQL() }
diff --git a/parser.go b/parser.go
index 708db3b..9894734 100644
--- a/parser.go
+++ b/parser.go
@@ -2304,6 +2304,8 @@ func (p *Parser) parseDDL() ast.DDL {
 			return p.parseCreateSchema(pos)
 		case p.Token.IsKeywordLike("DATABASE"):
 			return p.parseCreateDatabase(pos)
+		case p.Token.IsKeywordLike("PLACEMENT"):
+			return p.parseCreatePlacement(pos)
 		case p.Token.Kind == "PROTO":
 			return p.parseCreateProtoBundle(pos)
 		case p.Token.IsKeywordLike("TABLE"):
@@ -2411,6 +2413,7 @@ func (p *Parser) parseDropSchema(pos token.Pos) *ast.DropSchema {
 func (p *Parser) parseCreateDatabase(pos token.Pos) *ast.CreateDatabase {
 	p.expectKeywordLike("DATABASE")
 	name := p.parseIdent()
+
 	return &ast.CreateDatabase{
 		Create: pos,
 		Name:   name,
@@ -2430,6 +2433,18 @@ func (p *Parser) parseAlterDatabase(pos token.Pos) *ast.AlterDatabase {
 	}
 }
 
+func (p *Parser) parseCreatePlacement(pos token.Pos) *ast.CreatePlacement {
+	p.expectKeywordLike("PLACEMENT")
+	name := p.parseIdent()
+	options := p.parseOptions()
+
+	return &ast.CreatePlacement{
+		Create:  pos,
+		Name:    name,
+		Options: options,
+	}
+}
+
 func (p *Parser) parseProtoBundleTypes() *ast.ProtoBundleTypes {
 	lparen := p.expect("(").Pos
 	types := parseCommaSeparatedList(p, p.parseNamedType)


Test patch:
diff --git a/testdata/input/ddl/create_placement.sql b/testdata/input/ddl/create_placement.sql
new file mode 100644
index 0000000..5b19cd3
--- /dev/null
+++ b/testdata/input/ddl/create_placement.sql
@@ -0,0 +1,1 @@
+CREATE PLACEMENT `europeplacement` OPTIONS (instance_partition="europe-partition")
\ No newline at end of file
diff --git a/testdata/result/ddl/create_placement.sql.txt b/testdata/result/ddl/create_placement.sql.txt
new file mode 100644
index 0000000..efbb1bc
--- /dev/null
+++ b/testdata/result/ddl/create_placement.sql.txt
@@ -0,0 +1,32 @@
+--- create_placement.sql
+CREATE PLACEMENT `europeplacement` OPTIONS (instance_partition="europe-partition")
+--- AST
+&ast.CreatePlacement{
+  Create: 0,
+  Name:   &ast.Ident{
+    NamePos: 17,
+    NameEnd: 34,
+    Name:    "europeplacement",
+  },
+  Options: &ast.Options{
+    Options: 35,
+    Rparen:  81,
+    Records: []*ast.OptionsDef{
+      &ast.OptionsDef{
+        Name: &ast.Ident{
+          NamePos: 44,
+          NameEnd: 62,
+          Name:    "instance_partition",
+        },
+        Value: &ast.StringLiteral{
+          ValuePos: 63,
+          ValueEnd: 81,
+          Value:    "europe-partition",
+        },
+      },
+    },
+  },
+}
+
+--- SQL
+CREATE PLACEMENT europeplacement OPTIONS (instance_partition = "europe-partition") 
diff --git a/testdata/result/statement/create_placement.sql.txt b/testdata/result/statement/create_placement.sql.txt
new file mode 100644
index 0000000..efbb1bc
--- /dev/null
+++ b/testdata/result/statement/create_placement.sql.txt
@@ -0,0 +1,32 @@
+--- create_placement.sql
+CREATE PLACEMENT `europeplacement` OPTIONS (instance_partition="europe-partition")
+--- AST
+&ast.CreatePlacement{
+  Create: 0,
+  Name:   &ast.Ident{
+    NamePos: 17,
+    NameEnd: 34,
+    Name:    "europeplacement",
+  },
+  Options: &ast.Options{
+    Options: 35,
+    Rparen:  81,
+    Records: []*ast.OptionsDef{
+      &ast.OptionsDef{
+        Name: &ast.Ident{
+          NamePos: 44,
+          NameEnd: 62,
+          Name:    "instance_partition",
+        },
+        Value: &ast.StringLiteral{
+          ValuePos: 63,
+          ValueEnd: 81,
+          Value:    "europe-partition",
+        },
+      },
+    },
+  },
+}
+
+--- SQL
+CREATE PLACEMENT europeplacement OPTIONS (instance_partition = "europe-partition")