Repository: cloudspannerecosystem/memefish (go)

Issue:
Incompatibility: ANALYZE statement
This incompatibility is detected by #90.

## Spec

https://cloud.google.com/spanner/docs/reference/standard-sql/data-definition-language#analyze-statistics

## Reproducing input
```
ANALYZE
```

## Current behavior
```
syntax error:testdata/combined.test:2:1: expected pseudo keyword: ALTER, DROP, but: ANALYZE

  2:  ANALYZE
      ^~~~~~~
```

Pull request:
Implement ANALYZE statement
This PR implements `ANALYZE` DDL statement.

fixes #93 

Patch:
diff --git a/ast/ast.go b/ast/ast.go
index a6d215e..6956f65 100644
--- a/ast/ast.go
+++ b/ast/ast.go
@@ -64,6 +64,9 @@ func (CreateSchema) isStatement()       {}
 func (DropSchema) isStatement()         {}
 func (CreateDatabase) isStatement()     {}
 func (AlterDatabase) isStatement()      {}
+func (CreateProtoBundle) isStatement()  {}
+func (AlterProtoBundle) isStatement()   {}
+func (DropProtoBundle) isStatement()    {}
 func (CreateTable) isStatement()        {}
 func (AlterTable) isStatement()         {}
 func (DropTable) isStatement()          {}
@@ -87,6 +90,7 @@ func (CreateSequence) isStatement()     {}
 func (AlterSequence) isStatement()      {}
 func (DropSequence) isStatement()       {}
 func (AlterStatistics) isStatement()    {}
+func (Analyze) isStatement()            {}
 func (CreateVectorIndex) isStatement()  {}
 func (DropVectorIndex) isStatement()    {}
 func (Insert) isStatement()             {}
@@ -303,6 +307,9 @@ func (CreateSchema) isDDL()       {}
 func (DropSchema) isDDL()         {}
 func (CreateDatabase) isDDL()     {}
 func (AlterDatabase) isDDL()      {}
+func (CreateProtoBundle) isDDL()  {}
+func (AlterProtoBundle) isDDL()   {}
+func (DropProtoBundle) isDDL()    {}
 func (CreateTable) isDDL()        {}
 func (AlterTable) isDDL()         {}
 func (DropTable) isDDL()          {}
@@ -326,6 +333,7 @@ func (CreateSequence) isDDL()     {}
 func (AlterSequence) isDDL()      {}
 func (DropSequence) isDDL()       {}
 func (AlterStatistics) isDDL()    {}
+func (Analyze) isDDL()            {}
 func (CreateVectorIndex) isDDL()  {}
 func (DropVectorIndex) isDDL()    {}
 
@@ -1819,6 +1827,105 @@ type AlterDatabase struct {
 	Options *Options
 }
 
+// ================================================================================
+//
+// PROTO BUNDLE statements
+//
+// ================================================================================
+
+type ProtoBundleAlteration interface {
+	Node
+	isProtoBundleAlteration()
+}
+
+func (AlterProtoBundleInsert) isProtoBundleAlteration() {}
+func (AlterProtoBundleUpdate) isProtoBundleAlteration() {}
+func (AlterProtoBundleDelete) isProtoBundleAlteration() {}
+
+// ProtoBundleTypes is parenthesized Protocol Buffers type names node IN CREATE/ALTER PROTO BUNDLE statement.
+//
+//	({{.Types | sqlJoin ", "}})
+type ProtoBundleTypes struct {
+	// pos = Lparen
+	// end = Rparen + 1
+
+	Lparen, Rparen token.Pos
+	Types          []*NamedType
+}
+
+// CreateProtoBundle is CREATE PROTO BUNDLE statement node.
+//
+//	CREATE PROTO BUNDLE {{.Types, | sql}}
+type CreateProtoBundle struct {
+	// pos = Create
+	// end = Types.end
+
+	Create token.Pos // position of "CREATE" keyword
+
+	Types *ProtoBundleTypes
+}
+
+// AlterProtoBundle is ALTER PROTO BUNDLE statement node.
+//
+//	ALTER PROTO BUNDLE {{.Alteration | sql}}
+type AlterProtoBundle struct {
+	// pos = Alter
+	// end = Alteration.end
+
+	Alter token.Pos // position of "ALTER" keyword
+
+	Alteration ProtoBundleAlteration
+}
+
+// AlterProtoBundleInsert is INSERT (proto_type_name, ...) node in ALTER PROTO BUNDLE.
+//
+//	INSERT {{.Types | sql}}
+type AlterProtoBundleInsert struct {
+	// pos = Insert
+	// end = Types.end
+
+	Insert token.Pos // position of "INSERT" pseudo keyword
+
+	Types *ProtoBundleTypes
+}
+
+// AlterProtoBundleUpdate is UPDATE (proto_type_name, ...) node in ALTER PROTO BUNDLE.
+//
+//	UPDATE {{.Types | sql}}
+type AlterProtoBundleUpdate struct {
+	// pos = Update
+	// end = Types.end
+
+	Update token.Pos // position of "UPDATE" pseudo keyword
+
+	Types *ProtoBundleTypes
+}
+
+// AlterProtoBundleDelete is DELETE (proto_type_name, ...) node in ALTER PROTO BUNDLE.
+//
+//	DELETE {{.Types | sql}}
+type AlterProtoBundleDelete struct {
+	// pos = Delete
+	// end = Types.end
+
+	Delete token.Pos // position of "DELETE" pseudo keyword
+
+	Types *ProtoBundleTypes
+}
+
+// DropProtoBundle is DROP PROTO BUNDLE statement node.
+//
+//	DROP PROTO BUNDLE
+type DropProtoBundle struct {
+	// pos = Drop
+	// end = Bundle + 6
+
+	Drop   token.Pos // position of "DROP" pseudo keyword
+	Bundle token.Pos // position of "BUNDLE" pseudo keyword
+}
+
+// end of PROTO BUNDLE statements
+
 // CreateTable is CREATE TABLE statement node.
 //
 //	CREATE TABLE {{if .IfNotExists}}IF NOT EXISTS{{end}} {{.Name | sql}} (
@@ -2761,6 +2868,16 @@ type AlterStatistics struct {
 	Options *Options
 }
 
+// Analyze is ANALYZE statement node.
+//
+//	ANALYZE
+type Analyze struct {
+	// pos = Analyze
+	// end = Analyze + 7
+
+	Analyze token.Pos // position of "ANALYZE" keyword
+}
+
 // ================================================================================
 //
 // Types for Schema
diff --git a/ast/pos.go b/ast/pos.go
index 4bc700c..af1b319 100644
--- a/ast/pos.go
+++ b/ast/pos.go
@@ -822,6 +822,62 @@ func (a *AlterDatabase) End() token.Pos {
 	return nodeEnd(wrapNode(a.Name))
 }
 
+func (p *ProtoBundleTypes) Pos() token.Pos {
+	return p.Lparen
+}
+
+func (p *ProtoBundleTypes) End() token.Pos {
+	return posAdd(p.Rparen, 1)
+}
+
+func (c *CreateProtoBundle) Pos() token.Pos {
+	return c.Create
+}
+
+func (c *CreateProtoBundle) End() token.Pos {
+	return nodeEnd(wrapNode(c.Types))
+}
+
+func (a *AlterProtoBundle) Pos() token.Pos {
+	return a.Alter
+}
+
+func (a *AlterProtoBundle) End() token.Pos {
+	return nodeEnd(wrapNode(a.Alteration))
+}
+
+func (a *AlterProtoBundleInsert) Pos() token.Pos {
+	return a.Insert
+}
+
+func (a *AlterProtoBundleInsert) End() token.Pos {
+	return nodeEnd(wrapNode(a.Types))
+}
+
+func (a *AlterProtoBundleUpdate) Pos() token.Pos {
+	return a.Update
+}
+
+func (a *AlterProtoBundleUpdate) End() token.Pos {
+	return nodeEnd(wrapNode(a.Types))
+}
+
+func (a *AlterProtoBundleDelete) Pos() token.Pos {
+	return a.Delete
+}
+
+func (a *AlterProtoBundleDelete) End() token.Pos {
+	return nodeEnd(wrapNode(a.Types))
+}
+
+func (d *DropProtoBundle) Pos() token.Pos {
+	return d.Drop
+}
+
+func (d *DropProtoBundle) End() token.Pos {
+	return posAdd(d.Bundle, 6)
+}
+
 func (c *CreateTable) Pos() token.Pos {
 	return c.Create
 }
@@ -1390,6 +1446,14 @@ func (a *AlterStatistics) End() token.Pos {
 	return nodeEnd(wrapNode(a.Options))
 }
 
+func (a *Analyze) Pos() token.Pos {
+	return a.Analyze
+}
+
+func (a *Analyze) End() token.Pos {
+	return posAdd(a.Analyze, 7)
+}
+
 func (s *ScalarSchemaType) Pos() token.Pos {
 	return s.NamePos
 }
diff --git a/ast/sql.go b/ast/sql.go
index 2af3c5f..242b653 100644
--- a/ast/sql.go
+++ b/ast/sql.go
@@ -794,6 +794,20 @@ func (d *AlterDatabase) SQL() string {
 	return "ALTER DATABASE " + d.Name.SQL() + " SET " + d.Options.SQL()
 }
 
+func (p *ProtoBundleTypes) SQL() string { return "(" + sqlJoin(p.Types, ", ") + ")" }
+
+func (b *CreateProtoBundle) SQL() string { return "CREATE PROTO BUNDLE " + b.Types.SQL() }
+
+func (a *AlterProtoBundle) SQL() string { return "ALTER PROTO BUNDLE " + a.Alteration.SQL() }
+
+func (a *AlterProtoBundleInsert) SQL() string { return "INSERT " + a.Types.SQL() }
+
+func (a *AlterProtoBundleUpdate) SQL() string { return "UPDATE " + a.Types.SQL() }
+
+func (a *AlterProtoBundleDelete) SQL() string { return "DELETE " + a.Types.SQL() }
+
+func (d *DropProtoBundle) SQL() string { return "DROP PROTO BUNDLE" }
+
 func (c *CreateTable) SQL() string {
 	return "CREATE TABLE " +
 		strOpt(c.IfNotExists, "IF NOT EXISTS ") +
@@ -1256,6 +1270,7 @@ func (r *RolePrivilege) SQL() string {
 func (s *AlterStatistics) SQL() string {
 	return "ALTER STATISTICS " + s.Name.SQL() + " SET " + s.Options.SQL()
 }
+func (a *Analyze) SQL() string { return "ANALYZE" }
 
 // ================================================================================
 //
diff --git a/parser.go b/parser.go
index 57c9032..708db3b 100644
--- a/parser.go
+++ b/parser.go
@@ -213,7 +213,8 @@ func (p *Parser) parseStatement() ast.Statement {
 	case p.Token.Kind == "SELECT" || p.Token.Kind == "@" || p.Token.Kind == "WITH" || p.Token.Kind == "(":
 		return p.parseQueryStatement()
 	case p.Token.Kind == "CREATE" || p.Token.IsKeywordLike("ALTER") || p.Token.IsKeywordLike("DROP") ||
-		p.Token.IsKeywordLike("RENAME") || p.Token.IsKeywordLike("GRANT") || p.Token.IsKeywordLike("REVOKE"):
+		p.Token.IsKeywordLike("RENAME") || p.Token.IsKeywordLike("GRANT") || p.Token.IsKeywordLike("REVOKE") ||
+		p.Token.IsKeywordLike("ANALYZE"):
 		return p.parseDDL()
 	case p.Token.IsKeywordLike("INSERT") || p.Token.IsKeywordLike("DELETE") || p.Token.IsKeywordLike("UPDATE"):
 		return p.parseDML()
@@ -2303,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.Kind == "PROTO":
+			return p.parseCreateProtoBundle(pos)
 		case p.Token.IsKeywordLike("TABLE"):
 			return p.parseCreateTable(pos)
 		case p.Token.IsKeywordLike("SEQUENCE"):
@@ -2328,6 +2331,8 @@ func (p *Parser) parseDDL() ast.DDL {
 			return p.parseAlterTable(pos)
 		case p.Token.IsKeywordLike("DATABASE"):
 			return p.parseAlterDatabase(pos)
+		case p.Token.Kind == "PROTO":
+			return p.parseAlterProtoBundle(pos)
 		case p.Token.IsKeywordLike("INDEX"):
 			return p.parseAlterIndex(pos)
 		case p.Token.IsKeywordLike("SEARCH"):
@@ -2345,6 +2350,8 @@ func (p *Parser) parseDDL() ast.DDL {
 		switch {
 		case p.Token.IsKeywordLike("SCHEMA"):
 			return p.parseDropSchema(pos)
+		case p.Token.Kind == "PROTO":
+			return p.parseDropProtoBundle(pos)
 		case p.Token.IsKeywordLike("TABLE"):
 			return p.parseDropTable(pos)
 		case p.Token.IsKeywordLike("INDEX"):
@@ -2372,6 +2379,8 @@ func (p *Parser) parseDDL() ast.DDL {
 	case p.Token.IsKeywordLike("REVOKE"):
 		p.nextToken()
 		return p.parseRevoke(pos)
+	case p.Token.IsKeywordLike("ANALYZE"):
+		return p.parseAnalyze()
 	}
 
 	if p.Token.Kind != token.TokenIdent {
@@ -2421,6 +2430,47 @@ func (p *Parser) parseAlterDatabase(pos token.Pos) *ast.AlterDatabase {
 	}
 }
 
+func (p *Parser) parseProtoBundleTypes() *ast.ProtoBundleTypes {
+	lparen := p.expect("(").Pos
+	types := parseCommaSeparatedList(p, p.parseNamedType)
+	rparen := p.expect(")").Pos
+	return &ast.ProtoBundleTypes{
+		Lparen: lparen,
+		Rparen: rparen,
+		Types:  types,
+	}
+}
+
+func (p *Parser) parseCreateProtoBundle(pos token.Pos) *ast.CreateProtoBundle {
+	p.expect("PROTO")
+	p.expectKeywordLike("BUNDLE")
+	types := p.parseProtoBundleTypes()
+
+	return &ast.CreateProtoBundle{
+		Create: pos,
+		Types:  types,
+	}
+}
+
+func (p *Parser) parseAlterProtoBundle(pos token.Pos) *ast.AlterProtoBundle {
+	p.expect("PROTO")
+	p.expectKeywordLike("BUNDLE")
+	alteration := p.parseProtoBundleAlteration()
+	return &ast.AlterProtoBundle{
+		Alter:      pos,
+		Alteration: alteration,
+	}
+}
+
+func (p *Parser) parseDropProtoBundle(pos token.Pos) *ast.DropProtoBundle {
+	p.expect("PROTO")
+	bundle := p.expectKeywordLike("BUNDLE").Pos
+
+	return &ast.DropProtoBundle{
+		Drop:   pos,
+		Bundle: bundle,
+	}
+}
 func (p *Parser) parseCreateTable(pos token.Pos) *ast.CreateTable {
 	p.expectKeywordLike("TABLE")
 	ifNotExists := p.parseIfNotExists()
@@ -3689,6 +3739,14 @@ func (p *Parser) parseAlterStatistics(pos token.Pos) *ast.AlterStatistics {
 	}
 }
 
+func (p *Parser) parseAnalyze() *ast.Analyze {
+	pos := p.expectKeywordLike("ANALYZE").Pos
+
+	return &ast.Analyze{
+		Analyze: pos,
+	}
+}
+
 var scalarSchemaTypes = []string{
 	"BOOL",
 	"INT64",
@@ -4194,3 +4252,34 @@ func (p *Parser) parseRenameTable(pos token.Pos) *ast.RenameTable {
 	}
 
 }
+
+func (p *Parser) parseProtoBundleAlteration() ast.ProtoBundleAlteration {
+	switch {
+	case p.Token.IsKeywordLike("INSERT"):
+		insert := p.expectKeywordLike("INSERT").Pos
+		types := p.parseProtoBundleTypes()
+
+		return &ast.AlterProtoBundleInsert{
+			Insert: insert,
+			Types:  types,
+		}
+	case p.Token.IsKeywordLike("UPDATE"):
+		update := p.expectKeywordLike("UPDATE").Pos
+		types := p.parseProtoBundleTypes()
+
+		return &ast.AlterProtoBundleUpdate{
+			Update: update,
+			Types:  types,
+		}
+	case p.Token.IsKeywordLike("DELETE"):
+		delete := p.expectKeywordLike("DELETE").Pos
+		types := p.parseProtoBundleTypes()
+
+		return &ast.AlterProtoBundleDelete{
+			Delete: delete,
+			Types:  types,
+		}
+	default:
+		panic(p.errorfAtToken(&p.Token, `expected INSERT, UPDATE or DELETE, but: %v`, p.Token.AsString))
+	}
+}


Test patch:
diff --git a/testdata/input/ddl/alter_proto_bundle_delete.sql b/testdata/input/ddl/alter_proto_bundle_delete.sql
new file mode 100644
index 0000000..345efba
--- /dev/null
+++ b/testdata/input/ddl/alter_proto_bundle_delete.sql
@@ -0,0 +1,1 @@
+ALTER PROTO BUNDLE DELETE(`examples.shipping.OrderHistory`)
\ No newline at end of file
diff --git a/testdata/input/ddl/alter_proto_bundle_insert.sql b/testdata/input/ddl/alter_proto_bundle_insert.sql
new file mode 100644
index 0000000..8d70937
--- /dev/null
+++ b/testdata/input/ddl/alter_proto_bundle_insert.sql
@@ -0,0 +1,3 @@
+ALTER PROTO BUNDLE INSERT (
+  examples.shipping.OrderHistory
+)
\ No newline at end of file
diff --git a/testdata/input/ddl/alter_proto_bundle_update.sql b/testdata/input/ddl/alter_proto_bundle_update.sql
new file mode 100644
index 0000000..ab193b6
--- /dev/null
+++ b/testdata/input/ddl/alter_proto_bundle_update.sql
@@ -0,0 +1,1 @@
+ALTER PROTO BUNDLE UPDATE(`examples.shipping.Order`)
\ No newline at end of file
diff --git a/testdata/input/ddl/analyze.sql b/testdata/input/ddl/analyze.sql
new file mode 100644
index 0000000..b8f450d
--- /dev/null
+++ b/testdata/input/ddl/analyze.sql
@@ -0,0 +1,1 @@
+ANALYZE
\ No newline at end of file
diff --git a/testdata/input/ddl/create_proto_bundle_backquoted.sql b/testdata/input/ddl/create_proto_bundle_backquoted.sql
new file mode 100644
index 0000000..72674a3
--- /dev/null
+++ b/testdata/input/ddl/create_proto_bundle_backquoted.sql
@@ -0,0 +1,6 @@
+-- If you're using a protocol buffer type and any part of the type name is a Spanner reserved keyword,
+-- enclose the entire protocol buffer type name in backticks.
+CREATE PROTO BUNDLE (
+       `examples.shipping.Order`,
+       `examples.shipping.Order.Address`,
+       `examples.shipping.Order.Item`)
\ No newline at end of file
diff --git a/testdata/input/ddl/drop_proto_bunldle.sql b/testdata/input/ddl/drop_proto_bunldle.sql
new file mode 100644
index 0000000..a3d3142
--- /dev/null
+++ b/testdata/input/ddl/drop_proto_bunldle.sql
@@ -0,0 +1,1 @@
+DROP PROTO BUNDLE
\ No newline at end of file
diff --git a/testdata/result/ddl/alter_proto_bundle_delete.sql.txt b/testdata/result/ddl/alter_proto_bundle_delete.sql.txt
new file mode 100644
index 0000000..fb2dd12
--- /dev/null
+++ b/testdata/result/ddl/alter_proto_bundle_delete.sql.txt
@@ -0,0 +1,27 @@
+--- alter_proto_bundle_delete.sql
+ALTER PROTO BUNDLE DELETE(`examples.shipping.OrderHistory`)
+--- AST
+&ast.AlterProtoBundle{
+  Alter:      0,
+  Alteration: &ast.AlterProtoBundleDelete{
+    Delete: 19,
+    Types:  &ast.ProtoBundleTypes{
+      Lparen: 25,
+      Rparen: 58,
+      Types:  []*ast.NamedType{
+        &ast.NamedType{
+          Path: []*ast.Ident{
+            &ast.Ident{
+              NamePos: 26,
+              NameEnd: 58,
+              Name:    "examples.shipping.OrderHistory",
+            },
+          },
+        },
+      },
+    },
+  },
+}
+
+--- SQL
+ALTER PROTO BUNDLE DELETE (`examples.shipping.OrderHistory`)
diff --git a/testdata/result/ddl/alter_proto_bundle_insert.sql.txt b/testdata/result/ddl/alter_proto_bundle_insert.sql.txt
new file mode 100644
index 0000000..ebe692b
--- /dev/null
+++ b/testdata/result/ddl/alter_proto_bundle_insert.sql.txt
@@ -0,0 +1,39 @@
+--- alter_proto_bundle_insert.sql
+ALTER PROTO BUNDLE INSERT (
+  examples.shipping.OrderHistory
+)
+--- AST
+&ast.AlterProtoBundle{
+  Alter:      0,
+  Alteration: &ast.AlterProtoBundleInsert{
+    Insert: 19,
+    Types:  &ast.ProtoBundleTypes{
+      Lparen: 26,
+      Rparen: 61,
+      Types:  []*ast.NamedType{
+        &ast.NamedType{
+          Path: []*ast.Ident{
+            &ast.Ident{
+              NamePos: 30,
+              NameEnd: 38,
+              Name:    "examples",
+            },
+            &ast.Ident{
+              NamePos: 39,
+              NameEnd: 47,
+              Name:    "shipping",
+            },
+            &ast.Ident{
+              NamePos: 48,
+              NameEnd: 60,
+              Name:    "OrderHistory",
+            },
+          },
+        },
+      },
+    },
+  },
+}
+
+--- SQL
+ALTER PROTO BUNDLE INSERT (examples.shipping.OrderHistory)
diff --git a/testdata/result/ddl/alter_proto_bundle_update.sql.txt b/testdata/result/ddl/alter_proto_bundle_update.sql.txt
new file mode 100644
index 0000000..2069c3e
--- /dev/null
+++ b/testdata/result/ddl/alter_proto_bundle_update.sql.txt
@@ -0,0 +1,27 @@
+--- alter_proto_bundle_update.sql
+ALTER PROTO BUNDLE UPDATE(`examples.shipping.Order`)
+--- AST
+&ast.AlterProtoBundle{
+  Alter:      0,
+  Alteration: &ast.AlterProtoBundleUpdate{
+    Update: 19,
+    Types:  &ast.ProtoBundleTypes{
+      Lparen: 25,
+      Rparen: 51,
+      Types:  []*ast.NamedType{
+        &ast.NamedType{
+          Path: []*ast.Ident{
+            &ast.Ident{
+              NamePos: 26,
+              NameEnd: 51,
+              Name:    "examples.shipping.Order",
+            },
+          },
+        },
+      },
+    },
+  },
+}
+
+--- SQL
+ALTER PROTO BUNDLE UPDATE (`examples.shipping.Order`)
diff --git a/testdata/result/ddl/analyze.sql.txt b/testdata/result/ddl/analyze.sql.txt
new file mode 100644
index 0000000..44a712f
--- /dev/null
+++ b/testdata/result/ddl/analyze.sql.txt
@@ -0,0 +1,9 @@
+--- analyze.sql
+ANALYZE
+--- AST
+&ast.Analyze{
+  Analyze: 0,
+}
+
+--- SQL
+ANALYZE
diff --git a/testdata/result/ddl/create_proto_bundle_backquoted.sql.txt b/testdata/result/ddl/create_proto_bundle_backquoted.sql.txt
new file mode 100644
index 0000000..e61fa00
--- /dev/null
+++ b/testdata/result/ddl/create_proto_bundle_backquoted.sql.txt
@@ -0,0 +1,47 @@
+--- create_proto_bundle_backquoted.sql
+-- If you're using a protocol buffer type and any part of the type name is a Spanner reserved keyword,
+-- enclose the entire protocol buffer type name in backticks.
+CREATE PROTO BUNDLE (
+       `examples.shipping.Order`,
+       `examples.shipping.Order.Address`,
+       `examples.shipping.Order.Item`)
+--- AST
+&ast.CreateProtoBundle{
+  Create: 165,
+  Types:  &ast.ProtoBundleTypes{
+    Lparen: 185,
+    Rparen: 300,
+    Types:  []*ast.NamedType{
+      &ast.NamedType{
+        Path: []*ast.Ident{
+          &ast.Ident{
+            NamePos: 194,
+            NameEnd: 219,
+            Name:    "examples.shipping.Order",
+          },
+        },
+      },
+      &ast.NamedType{
+        Path: []*ast.Ident{
+          &ast.Ident{
+            NamePos: 228,
+            NameEnd: 261,
+            Name:    "examples.shipping.Order.Address",
+          },
+        },
+      },
+      &ast.NamedType{
+        Path: []*ast.Ident{
+          &ast.Ident{
+            NamePos: 270,
+            NameEnd: 300,
+            Name:    "examples.shipping.Order.Item",
+          },
+        },
+      },
+    },
+  },
+}
+
+--- SQL
+CREATE PROTO BUNDLE (`examples.shipping.Order`, `examples.shipping.Order.Address`, `examples.shipping.Order.Item`)
diff --git a/testdata/result/ddl/drop_proto_bunldle.sql.txt b/testdata/result/ddl/drop_proto_bunldle.sql.txt
new file mode 100644
index 0000000..b36d664
--- /dev/null
+++ b/testdata/result/ddl/drop_proto_bunldle.sql.txt
@@ -0,0 +1,10 @@
+--- drop_proto_bunldle.sql
+DROP PROTO BUNDLE
+--- AST
+&ast.DropProtoBundle{
+  Drop:   0,
+  Bundle: 11,
+}
+
+--- SQL
+DROP PROTO BUNDLE
diff --git a/testdata/result/statement/alter_proto_bundle_delete.sql.txt b/testdata/result/statement/alter_proto_bundle_delete.sql.txt
new file mode 100644
index 0000000..fb2dd12
--- /dev/null
+++ b/testdata/result/statement/alter_proto_bundle_delete.sql.txt
@@ -0,0 +1,27 @@
+--- alter_proto_bundle_delete.sql
+ALTER PROTO BUNDLE DELETE(`examples.shipping.OrderHistory`)
+--- AST
+&ast.AlterProtoBundle{
+  Alter:      0,
+  Alteration: &ast.AlterProtoBundleDelete{
+    Delete: 19,
+    Types:  &ast.ProtoBundleTypes{
+      Lparen: 25,
+      Rparen: 58,
+      Types:  []*ast.NamedType{
+        &ast.NamedType{
+          Path: []*ast.Ident{
+            &ast.Ident{
+              NamePos: 26,
+              NameEnd: 58,
+              Name:    "examples.shipping.OrderHistory",
+            },
+          },
+        },
+      },
+    },
+  },
+}
+
+--- SQL
+ALTER PROTO BUNDLE DELETE (`examples.shipping.OrderHistory`)
diff --git a/testdata/result/statement/alter_proto_bundle_insert.sql.txt b/testdata/result/statement/alter_proto_bundle_insert.sql.txt
new file mode 100644
index 0000000..ebe692b
--- /dev/null
+++ b/testdata/result/statement/alter_proto_bundle_insert.sql.txt
@@ -0,0 +1,39 @@
+--- alter_proto_bundle_insert.sql
+ALTER PROTO BUNDLE INSERT (
+  examples.shipping.OrderHistory
+)
+--- AST
+&ast.AlterProtoBundle{
+  Alter:      0,
+  Alteration: &ast.AlterProtoBundleInsert{
+    Insert: 19,
+    Types:  &ast.ProtoBundleTypes{
+      Lparen: 26,
+      Rparen: 61,
+      Types:  []*ast.NamedType{
+        &ast.NamedType{
+          Path: []*ast.Ident{
+            &ast.Ident{
+              NamePos: 30,
+              NameEnd: 38,
+              Name:    "examples",
+            },
+            &ast.Ident{
+              NamePos: 39,
+              NameEnd: 47,
+              Name:    "shipping",
+            },
+            &ast.Ident{
+              NamePos: 48,
+              NameEnd: 60,
+              Name:    "OrderHistory",
+            },
+          },
+        },
+      },
+    },
+  },
+}
+
+--- SQL
+ALTER PROTO BUNDLE INSERT (examples.shipping.OrderHistory)
diff --git a/testdata/result/statement/alter_proto_bundle_update.sql.txt b/testdata/result/statement/alter_proto_bundle_update.sql.txt
new file mode 100644
index 0000000..2069c3e
--- /dev/null
+++ b/testdata/result/statement/alter_proto_bundle_update.sql.txt
@@ -0,0 +1,27 @@
+--- alter_proto_bundle_update.sql
+ALTER PROTO BUNDLE UPDATE(`examples.shipping.Order`)
+--- AST
+&ast.AlterProtoBundle{
+  Alter:      0,
+  Alteration: &ast.AlterProtoBundleUpdate{
+    Update: 19,
+    Types:  &ast.ProtoBundleTypes{
+      Lparen: 25,
+      Rparen: 51,
+      Types:  []*ast.NamedType{
+        &ast.NamedType{
+          Path: []*ast.Ident{
+            &ast.Ident{
+              NamePos: 26,
+              NameEnd: 51,
+              Name:    "examples.shipping.Order",
+            },
+          },
+        },
+      },
+    },
+  },
+}
+
+--- SQL
+ALTER PROTO BUNDLE UPDATE (`examples.shipping.Order`)
diff --git a/testdata/result/statement/analyze.sql.txt b/testdata/result/statement/analyze.sql.txt
new file mode 100644
index 0000000..44a712f
--- /dev/null
+++ b/testdata/result/statement/analyze.sql.txt
@@ -0,0 +1,9 @@
+--- analyze.sql
+ANALYZE
+--- AST
+&ast.Analyze{
+  Analyze: 0,
+}
+
+--- SQL
+ANALYZE
diff --git a/testdata/result/statement/create_proto_bundle_backquoted.sql.txt b/testdata/result/statement/create_proto_bundle_backquoted.sql.txt
new file mode 100644
index 0000000..e61fa00
--- /dev/null
+++ b/testdata/result/statement/create_proto_bundle_backquoted.sql.txt
@@ -0,0 +1,47 @@
+--- create_proto_bundle_backquoted.sql
+-- If you're using a protocol buffer type and any part of the type name is a Spanner reserved keyword,
+-- enclose the entire protocol buffer type name in backticks.
+CREATE PROTO BUNDLE (
+       `examples.shipping.Order`,
+       `examples.shipping.Order.Address`,
+       `examples.shipping.Order.Item`)
+--- AST
+&ast.CreateProtoBundle{
+  Create: 165,
+  Types:  &ast.ProtoBundleTypes{
+    Lparen: 185,
+    Rparen: 300,
+    Types:  []*ast.NamedType{
+      &ast.NamedType{
+        Path: []*ast.Ident{
+          &ast.Ident{
+            NamePos: 194,
+            NameEnd: 219,
+            Name:    "examples.shipping.Order",
+          },
+        },
+      },
+      &ast.NamedType{
+        Path: []*ast.Ident{
+          &ast.Ident{
+            NamePos: 228,
+            NameEnd: 261,
+            Name:    "examples.shipping.Order.Address",
+          },
+        },
+      },
+      &ast.NamedType{
+        Path: []*ast.Ident{
+          &ast.Ident{
+            NamePos: 270,
+            NameEnd: 300,
+            Name:    "examples.shipping.Order.Item",
+          },
+        },
+      },
+    },
+  },
+}
+
+--- SQL
+CREATE PROTO BUNDLE (`examples.shipping.Order`, `examples.shipping.Order.Address`, `examples.shipping.Order.Item`)
diff --git a/testdata/result/statement/drop_proto_bunldle.sql.txt b/testdata/result/statement/drop_proto_bunldle.sql.txt
new file mode 100644
index 0000000..b36d664
--- /dev/null
+++ b/testdata/result/statement/drop_proto_bunldle.sql.txt
@@ -0,0 +1,10 @@
+--- drop_proto_bunldle.sql
+DROP PROTO BUNDLE
+--- AST
+&ast.DropProtoBundle{
+  Drop:   0,
+  Bundle: 11,
+}
+
+--- SQL
+DROP PROTO BUNDLE