Repository: cloudspannerecosystem/memefish
Pull request #61 (2023-08-24 04:03:15)

Add the support of the IF NOT EXISTS when creating table
Spanner now supports using IF NOT EXISTS when creating the table, please refer: https://cloud.google.com/spanner/docs/reference/standard-sql/data-definition-language#create_table


Patch:
diff --git a/ast/ast.go b/ast/ast.go
index fdf0cbf..a333535 100644
--- a/ast/ast.go
+++ b/ast/ast.go
@@ -53,6 +53,7 @@ type Statement interface {
 func (QueryStatement) isStatement() {}
 func (CreateDatabase) isStatement() {}
 func (CreateTable) isStatement()    {}
+func (CreateSequence) isStatement() {}
 func (CreateView) isStatement()     {}
 func (CreateIndex) isStatement()    {}
 func (CreateRole) isStatement()     {}
@@ -207,6 +208,7 @@ type DDL interface {
 func (CreateDatabase) isDDL() {}
 func (CreateTable) isDDL()    {}
 func (CreateView) isDDL()     {}
+func (CreateSequence) isDDL() {}
 func (AlterTable) isDDL()     {}
 func (DropTable) isDDL()      {}
 func (CreateIndex) isDDL()    {}
@@ -1324,7 +1326,7 @@ type CreateDatabase struct {
 
 // CreateTable is CREATE TABLE statement node.
 //
-//	CREATE TABLE {{.Name | sql}} (
+//	CREATE TABLE {{if .IfNotExists}}IF NOT EXISTS{{end}} {{.Name | sql}} (
 //	  {{.Columns | sqlJoin ","}}
 //	  {{if and .Columns .TableConstrains}},{{end}}{{.TableConstraints | sqlJoin ","}}
 //	)
@@ -1342,6 +1344,7 @@ type CreateTable struct {
 	Create token.Pos // position of "CREATE" keyword
 	Rparen token.Pos // position of ")" of PRIMARY KEY clause
 
+	IfNotExists       bool
 	Name              *Ident
 	Columns           []*ColumnDef
 	TableConstraints  []*TableConstraint
@@ -1350,6 +1353,21 @@ type CreateTable struct {
 	RowDeletionPolicy *CreateRowDeletionPolicy // optional
 }
 
+// CreateSequence is CREATE SEQUENCE statement node.
+//
+//	CREATE SEQUENCE IF NOT EXISTS {{.Name | sql}} }} OPTIONS ({{.Options | sqlJoin ","}})
+type CreateSequence struct {
+	// pos = Create
+	// end = Rparen + 1
+
+	Create token.Pos // position of "CREATE" keyword
+	Rparen token.Pos // position of ")" of OPTIONS clause
+
+	Name        *Ident
+	IfNotExists bool
+	Options     []*SequenceOption // len(Options) > 0
+}
+
 // ColumnDef is column definition in CREATE TABLE.
 //
 //	{{.Name | sql}}
@@ -1963,3 +1981,14 @@ type UpdateItem struct {
 	Path []*Ident // len(Path) > 0
 	Expr Expr
 }
+
+// SequenceOption is option for CREATE SEQUENCE.
+//
+//	{{.Name | sql}} = {{.Value | sql}}
+type SequenceOption struct {
+	// pos = Name.pos
+	// end = Value.end
+
+	Name  *Ident
+	Value Expr
+}
diff --git a/ast/pos.go b/ast/pos.go
index e638d0c..a8085e5 100644
--- a/ast/pos.go
+++ b/ast/pos.go
@@ -495,6 +495,14 @@ func (c *CreateTable) End() token.Pos {
 	return c.Rparen + 1
 }
 
+func (c *CreateSequence) Pos() token.Pos {
+	return c.Create
+}
+
+func (c *CreateSequence) End() token.Pos {
+	return c.Rparen + 1
+}
+
 func (c *CreateView) Pos() token.Pos {
 	return c.Create
 }
@@ -740,3 +748,6 @@ func (u *Update) End() token.Pos { return u.Where.End() }
 
 func (u *UpdateItem) Pos() token.Pos { return u.Path[0].Pos() }
 func (u *UpdateItem) End() token.Pos { return u.Expr.End() }
+
+func (o *SequenceOption) Pos() token.Pos { return o.Name.Pos() }
+func (o *SequenceOption) End() token.Pos { return o.Value.End() }
diff --git a/ast/sql.go b/ast/sql.go
index f435f99..facfbcb 100644
--- a/ast/sql.go
+++ b/ast/sql.go
@@ -716,7 +716,11 @@ func (c *CreateDatabase) SQL() string {
 }
 
 func (c *CreateTable) SQL() string {
-	sql := "CREATE TABLE " + c.Name.SQL() + " ("
+	sql := "CREATE TABLE "
+	if c.IfNotExists {
+		sql += "IF NOT EXISTS "
+	}
+	sql += c.Name.SQL() + " ("
 	for i, c := range c.Columns {
 		if i != 0 {
 			sql += ", "
@@ -744,6 +748,22 @@ func (c *CreateTable) SQL() string {
 	return sql
 }
 
+func (c *CreateSequence) SQL() string {
+	sql := "CREATE SEQUENCE "
+	if c.IfNotExists {
+		sql += "IF NOT EXISTS "
+	}
+	sql += c.Name.SQL() + " OPTIONS ("
+	for i, o := range c.Options {
+		if i > 0 {
+			sql += ", "
+		}
+		sql += o.SQL()
+	}
+	sql += ")"
+	return sql
+}
+
 func (c *CreateView) SQL() string {
 	sql := "CREATE"
 	if c.OrReplace {
@@ -1108,3 +1128,7 @@ func (u *UpdateItem) SQL() string {
 	sql += " = " + u.Expr.SQL()
 	return sql
 }
+
+func (o *SequenceOption) SQL() string {
+	return o.Name.SQL() + " = " + o.Value.SQL()
+}
diff --git a/parser.go b/parser.go
index 75d53df..60155d0 100644
--- a/parser.go
+++ b/parser.go
@@ -1946,6 +1946,8 @@ func (p *Parser) parseDDL() ast.DDL {
 			return p.parseCreateDatabase(pos)
 		case p.Token.IsKeywordLike("TABLE"):
 			return p.parseCreateTable(pos)
+		case p.Token.IsKeywordLike("SEQUENCE"):
+			return p.parseCreateSequence(pos)
 		case p.Token.IsKeywordLike("VIEW") || p.Token.Kind == "OR":
 			return p.parseCreateView(pos)
 		case p.Token.IsKeywordLike("INDEX") || p.Token.IsKeywordLike("UNIQUE") || p.Token.IsKeywordLike("NULL_FILTERED"):
@@ -1994,6 +1996,13 @@ func (p *Parser) parseCreateDatabase(pos token.Pos) *ast.CreateDatabase {
 
 func (p *Parser) parseCreateTable(pos token.Pos) *ast.CreateTable {
 	p.expectKeywordLike("TABLE")
+	ifNotExists := false
+	if p.Token.IsKeywordLike("IF") {
+		p.nextToken()
+		p.expect("NOT")
+		p.expect("EXISTS")
+		ifNotExists = true
+	}
 	name := p.parseIdent()
 
 	// This loop allows parsing trailing comma intentionally.
@@ -2047,6 +2056,7 @@ func (p *Parser) parseCreateTable(pos token.Pos) *ast.CreateTable {
 	return &ast.CreateTable{
 		Create:            pos,
 		Rparen:            rparen,
+		IfNotExists:       ifNotExists,
 		Name:              name,
 		Columns:           columns,
 		TableConstraints:  constraints,
@@ -2056,6 +2066,59 @@ func (p *Parser) parseCreateTable(pos token.Pos) *ast.CreateTable {
 	}
 }
 
+func (p *Parser) parseCreateSequence(pos token.Pos) *ast.CreateSequence {
+	p.expectKeywordLike("SEQUENCE")
+	ifNotExists := false
+	if p.Token.IsKeywordLike("IF") {
+		p.nextToken()
+		p.expect("NOT")
+		p.expect("EXISTS")
+		ifNotExists = true
+	}
+	name := p.parseIdent()
+
+	p.expectKeywordLike("OPTIONS")
+	p.expect("(")
+	var options []*ast.SequenceOption
+	for p.Token.Kind != token.TokenEOF {
+		if p.Token.Kind == ")" {
+			break
+		}
+		optionName := p.parseIdent()
+		p.expect("=")
+		var value ast.Expr
+		switch p.Token.Kind {
+		case token.TokenInt:
+			value = p.parseIntLiteral()
+		case token.TokenString:
+			value = p.parseStringLiteral()
+		default:
+			p.panicfAtToken(&p.Token, "expected token: <int>, <string>, but: %s", p.Token.Kind)
+		}
+		options = append(options, &ast.SequenceOption{
+			Name:  optionName,
+			Value: value,
+		})
+		if p.Token.Kind != "," {
+			break
+		}
+		p.nextToken()
+
+	}
+	if len(options) == 0 {
+		p.panicfAtToken(&p.Token, "required at least one option")
+	}
+
+	rparent := p.expect(")").Pos
+	return &ast.CreateSequence{
+		Create:      pos,
+		Rparen:      rparent,
+		Name:        name,
+		IfNotExists: ifNotExists,
+		Options:     options,
+	}
+}
+
 func (p *Parser) parseCreateView(pos token.Pos) *ast.CreateView {
 	var orReplace bool
 	if p.Token.Kind == "OR" {


Test patch:
diff --git a/ast/ast_test.go b/ast/ast_test.go
index 8400cfd..ef603cb 100644
--- a/ast/ast_test.go
+++ b/ast/ast_test.go
@@ -9,6 +9,7 @@ func TestStatement(t *testing.T) {
 	Statement(&CreateDatabase{}).isStatement()
 	Statement(&CreateTable{}).isStatement()
 	Statement(&CreateIndex{}).isStatement()
+	Statement(&CreateSequence{}).isStatement()
 	Statement(&AlterTable{}).isStatement()
 	Statement(&DropTable{}).isStatement()
 	Statement(&DropIndex{}).isStatement()
@@ -111,6 +112,7 @@ func TestDDL(t *testing.T) {
 	DDL(&CreateDatabase{}).isDDL()
 	DDL(&CreateTable{}).isDDL()
 	DDL(&CreateIndex{}).isDDL()
+	DDL(&CreateSequence{}).isDDL()
 	DDL(&AlterTable{}).isDDL()
 	DDL(&DropTable{}).isDDL()
 	DDL(&DropIndex{}).isDDL()
diff --git a/testdata/input/ddl/create_sequance.sql b/testdata/input/ddl/create_sequance.sql
new file mode 100644
index 0000000..46d7f7a
--- /dev/null
+++ b/testdata/input/ddl/create_sequance.sql
@@ -0,0 +1,5 @@
+CREATE SEQUENCE IF NOT EXISTS MySequence OPTIONS (
+    sequence_kind='bit_reversed_positive',
+    skip_range_min = 1,
+    skip_range_max = 1000,
+    start_with_counter = 50)
diff --git a/testdata/input/ddl/create_table_if_not_exists.sql b/testdata/input/ddl/create_table_if_not_exists.sql
new file mode 100644
index 0000000..b361cb8
--- /dev/null
+++ b/testdata/input/ddl/create_table_if_not_exists.sql
@@ -0,0 +1,4 @@
+create table if not exists foo (
+  foo int64,
+  bar float64 not null,
+) primary key (foo, bar)
diff --git a/testdata/result/ddl/create_sequance.sql.txt b/testdata/result/ddl/create_sequance.sql.txt
new file mode 100644
index 0000000..37c68aa
--- /dev/null
+++ b/testdata/result/ddl/create_sequance.sql.txt
@@ -0,0 +1,74 @@
+--- create_sequance.sql
+CREATE SEQUENCE IF NOT EXISTS MySequence OPTIONS (
+    sequence_kind='bit_reversed_positive',
+    skip_range_min = 1,
+    skip_range_max = 1000,
+    start_with_counter = 50)
+
+--- AST
+&ast.CreateSequence{
+  Create: 0,
+  Rparen: 172,
+  Name:   &ast.Ident{
+    NamePos: 30,
+    NameEnd: 40,
+    Name:    "MySequence",
+  },
+  IfNotExists: true,
+  Options:     []*ast.SequenceOption{
+    &ast.SequenceOption{
+      Name: &ast.Ident{
+        NamePos: 55,
+        NameEnd: 68,
+        Name:    "sequence_kind",
+      },
+      Value: &ast.StringLiteral{
+        ValuePos: 69,
+        ValueEnd: 92,
+        Value:    "bit_reversed_positive",
+      },
+    },
+    &ast.SequenceOption{
+      Name: &ast.Ident{
+        NamePos: 98,
+        NameEnd: 112,
+        Name:    "skip_range_min",
+      },
+      Value: &ast.IntLiteral{
+        ValuePos: 115,
+        ValueEnd: 116,
+        Base:     10,
+        Value:    "1",
+      },
+    },
+    &ast.SequenceOption{
+      Name: &ast.Ident{
+        NamePos: 122,
+        NameEnd: 136,
+        Name:    "skip_range_max",
+      },
+      Value: &ast.IntLiteral{
+        ValuePos: 139,
+        ValueEnd: 143,
+        Base:     10,
+        Value:    "1000",
+      },
+    },
+    &ast.SequenceOption{
+      Name: &ast.Ident{
+        NamePos: 149,
+        NameEnd: 167,
+        Name:    "start_with_counter",
+      },
+      Value: &ast.IntLiteral{
+        ValuePos: 170,
+        ValueEnd: 172,
+        Base:     10,
+        Value:    "50",
+      },
+    },
+  },
+}
+
+--- SQL
+CREATE SEQUENCE IF NOT EXISTS MySequence OPTIONS (sequence_kind = "bit_reversed_positive", skip_range_min = 1, skip_range_max = 1000, start_with_counter = 50)
diff --git a/testdata/result/ddl/create_table.sql.txt b/testdata/result/ddl/create_table.sql.txt
index 4622af8..a96ca0e 100644
--- a/testdata/result/ddl/create_table.sql.txt
+++ b/testdata/result/ddl/create_table.sql.txt
@@ -14,9 +14,10 @@ create table foo (
 
 --- AST
 &ast.CreateTable{
-  Create: 0,
-  Rparen: 447,
-  Name:   &ast.Ident{
+  Create:      0,
+  Rparen:      447,
+  IfNotExists: false,
+  Name:        &ast.Ident{
     NamePos: 13,
     NameEnd: 16,
     Name:    "foo",
diff --git a/testdata/result/ddl/create_table_cluster.sql.txt b/testdata/result/ddl/create_table_cluster.sql.txt
index 7569729..8eb83c3 100644
--- a/testdata/result/ddl/create_table_cluster.sql.txt
+++ b/testdata/result/ddl/create_table_cluster.sql.txt
@@ -7,9 +7,10 @@ create table foo (
 
 --- AST
 &ast.CreateTable{
-  Create: 0,
-  Rparen: 59,
-  Name:   &ast.Ident{
+  Create:      0,
+  Rparen:      59,
+  IfNotExists: false,
+  Name:        &ast.Ident{
     NamePos: 13,
     NameEnd: 16,
     Name:    "foo",
diff --git a/testdata/result/ddl/create_table_cluster_and_row_deletion_policy.sql.txt b/testdata/result/ddl/create_table_cluster_and_row_deletion_policy.sql.txt
index 7f6e638..26aa3fb 100644
--- a/testdata/result/ddl/create_table_cluster_and_row_deletion_policy.sql.txt
+++ b/testdata/result/ddl/create_table_cluster_and_row_deletion_policy.sql.txt
@@ -9,9 +9,10 @@ create table foo (
 
 --- AST
 &ast.CreateTable{
-  Create: 0,
-  Rparen: 77,
-  Name:   &ast.Ident{
+  Create:      0,
+  Rparen:      77,
+  IfNotExists: false,
+  Name:        &ast.Ident{
     NamePos: 13,
     NameEnd: 16,
     Name:    "foo",
diff --git a/testdata/result/ddl/create_table_cluster_on_delete_no_action.sql.txt b/testdata/result/ddl/create_table_cluster_on_delete_no_action.sql.txt
index 8802ba7..c2caa5c 100644
--- a/testdata/result/ddl/create_table_cluster_on_delete_no_action.sql.txt
+++ b/testdata/result/ddl/create_table_cluster_on_delete_no_action.sql.txt
@@ -6,9 +6,10 @@ create table foo (
              on delete no action
 --- AST
 &ast.CreateTable{
-  Create: 0,
-  Rparen: 50,
-  Name:   &ast.Ident{
+  Create:      0,
+  Rparen:      50,
+  IfNotExists: false,
+  Name:        &ast.Ident{
     NamePos: 13,
     NameEnd: 16,
     Name:    "foo",
diff --git a/testdata/result/ddl/create_table_cluster_set_on_delete.sql.txt b/testdata/result/ddl/create_table_cluster_set_on_delete.sql.txt
index 093b5cc..4de2bcc 100644
--- a/testdata/result/ddl/create_table_cluster_set_on_delete.sql.txt
+++ b/testdata/result/ddl/create_table_cluster_set_on_delete.sql.txt
@@ -6,9 +6,10 @@ create table foo (
              on delete cascade
 --- AST
 &ast.CreateTable{
-  Create: 0,
-  Rparen: 49,
-  Name:   &ast.Ident{
+  Create:      0,
+  Rparen:      49,
+  IfNotExists: false,
+  Name:        &ast.Ident{
     NamePos: 13,
     NameEnd: 16,
     Name:    "foo",
diff --git a/testdata/result/ddl/create_table_if_not_exists.sql.txt b/testdata/result/ddl/create_table_if_not_exists.sql.txt
new file mode 100644
index 0000000..1677b5c
--- /dev/null
+++ b/testdata/result/ddl/create_table_if_not_exists.sql.txt
@@ -0,0 +1,77 @@
+--- create_table_if_not_exists.sql
+create table if not exists foo (
+  foo int64,
+  bar float64 not null,
+) primary key (foo, bar)
+
+--- AST
+&ast.CreateTable{
+  Create:      0,
+  Rparen:      93,
+  IfNotExists: true,
+  Name:        &ast.Ident{
+    NamePos: 27,
+    NameEnd: 30,
+    Name:    "foo",
+  },
+  Columns: []*ast.ColumnDef{
+    &ast.ColumnDef{
+      Null: -1,
+      Name: &ast.Ident{
+        NamePos: 35,
+        NameEnd: 38,
+        Name:    "foo",
+      },
+      Type: &ast.ScalarSchemaType{
+        NamePos: 39,
+        Name:    "INT64",
+      },
+      NotNull:       false,
+      DefaultExpr:   (*ast.ColumnDefaultExpr)(nil),
+      GeneratedExpr: (*ast.GeneratedColumnExpr)(nil),
+      Options:       (*ast.ColumnDefOptions)(nil),
+    },
+    &ast.ColumnDef{
+      Null: 64,
+      Name: &ast.Ident{
+        NamePos: 48,
+        NameEnd: 51,
+        Name:    "bar",
+      },
+      Type: &ast.ScalarSchemaType{
+        NamePos: 52,
+        Name:    "FLOAT64",
+      },
+      NotNull:       true,
+      DefaultExpr:   (*ast.ColumnDefaultExpr)(nil),
+      GeneratedExpr: (*ast.GeneratedColumnExpr)(nil),
+      Options:       (*ast.ColumnDefOptions)(nil),
+    },
+  },
+  TableConstraints: []*ast.TableConstraint(nil),
+  PrimaryKeys:      []*ast.IndexKey{
+    &ast.IndexKey{
+      DirPos: -1,
+      Name:   &ast.Ident{
+        NamePos: 85,
+        NameEnd: 88,
+        Name:    "foo",
+      },
+      Dir: "",
+    },
+    &ast.IndexKey{
+      DirPos: -1,
+      Name:   &ast.Ident{
+        NamePos: 90,
+        NameEnd: 93,
+        Name:    "bar",
+      },
+      Dir: "",
+    },
+  },
+  Cluster:           (*ast.Cluster)(nil),
+  RowDeletionPolicy: (*ast.CreateRowDeletionPolicy)(nil),
+}
+
+--- SQL
+CREATE TABLE IF NOT EXISTS foo (foo INT64, bar FLOAT64 NOT NULL) PRIMARY KEY (foo, bar)
diff --git a/testdata/result/ddl/create_table_row_deletion_policy.sql.txt b/testdata/result/ddl/create_table_row_deletion_policy.sql.txt
index fe72eed..4a49748 100644
--- a/testdata/result/ddl/create_table_row_deletion_policy.sql.txt
+++ b/testdata/result/ddl/create_table_row_deletion_policy.sql.txt
@@ -8,9 +8,10 @@ create table foo (
 
 --- AST
 &ast.CreateTable{
-  Create: 0,
-  Rparen: 77,
-  Name:   &ast.Ident{
+  Create:      0,
+  Rparen:      77,
+  IfNotExists: false,
+  Name:        &ast.Ident{
     NamePos: 13,
     NameEnd: 16,
     Name:    "foo",
diff --git a/testdata/result/ddl/create_table_trailing_comma.sql.txt b/testdata/result/ddl/create_table_trailing_comma.sql.txt
index a7a8aa8..0ff16b9 100644
--- a/testdata/result/ddl/create_table_trailing_comma.sql.txt
+++ b/testdata/result/ddl/create_table_trailing_comma.sql.txt
@@ -9,9 +9,10 @@ create table foo (
 
 --- AST
 &ast.CreateTable{
-  Create: 0,
-  Rparen: 83,
-  Name:   &ast.Ident{
+  Create:      0,
+  Rparen:      83,
+  IfNotExists: false,
+  Name:        &ast.Ident{
     NamePos: 13,
     NameEnd: 16,
     Name:    "foo",
diff --git a/testdata/result/ddl/create_table_types.sql.txt b/testdata/result/ddl/create_table_types.sql.txt
index 71d7773..349c0f4 100644
--- a/testdata/result/ddl/create_table_types.sql.txt
+++ b/testdata/result/ddl/create_table_types.sql.txt
@@ -15,9 +15,10 @@ create table types (
 
 --- AST
 &ast.CreateTable{
-  Create: 0,
-  Rparen: 212,
-  Name:   &ast.Ident{
+  Create:      0,
+  Rparen:      212,
+  IfNotExists: false,
+  Name:        &ast.Ident{
     NamePos: 13,
     NameEnd: 18,
     Name:    "types",
diff --git a/testdata/result/statement/create_sequance.sql.txt b/testdata/result/statement/create_sequance.sql.txt
new file mode 100644
index 0000000..37c68aa
--- /dev/null
+++ b/testdata/result/statement/create_sequance.sql.txt
@@ -0,0 +1,74 @@
+--- create_sequance.sql
+CREATE SEQUENCE IF NOT EXISTS MySequence OPTIONS (
+    sequence_kind='bit_reversed_positive',
+    skip_range_min = 1,
+    skip_range_max = 1000,
+    start_with_counter = 50)
+
+--- AST
+&ast.CreateSequence{
+  Create: 0,
+  Rparen: 172,
+  Name:   &ast.Ident{
+    NamePos: 30,
+    NameEnd: 40,
+    Name:    "MySequence",
+  },
+  IfNotExists: true,
+  Options:     []*ast.SequenceOption{
+    &ast.SequenceOption{
+      Name: &ast.Ident{
+        NamePos: 55,
+        NameEnd: 68,
+        Name:    "sequence_kind",
+      },
+      Value: &ast.StringLiteral{
+        ValuePos: 69,
+        ValueEnd: 92,
+        Value:    "bit_reversed_positive",
+      },
+    },
+    &ast.SequenceOption{
+      Name: &ast.Ident{
+        NamePos: 98,
+        NameEnd: 112,
+        Name:    "skip_range_min",
+      },
+      Value: &ast.IntLiteral{
+        ValuePos: 115,
+        ValueEnd: 116,
+        Base:     10,
+        Value:    "1",
+      },
+    },
+    &ast.SequenceOption{
+      Name: &ast.Ident{
+        NamePos: 122,
+        NameEnd: 136,
+        Name:    "skip_range_max",
+      },
+      Value: &ast.IntLiteral{
+        ValuePos: 139,
+        ValueEnd: 143,
+        Base:     10,
+        Value:    "1000",
+      },
+    },
+    &ast.SequenceOption{
+      Name: &ast.Ident{
+        NamePos: 149,
+        NameEnd: 167,
+        Name:    "start_with_counter",
+      },
+      Value: &ast.IntLiteral{
+        ValuePos: 170,
+        ValueEnd: 172,
+        Base:     10,
+        Value:    "50",
+      },
+    },
+  },
+}
+
+--- SQL
+CREATE SEQUENCE IF NOT EXISTS MySequence OPTIONS (sequence_kind = "bit_reversed_positive", skip_range_min = 1, skip_range_max = 1000, start_with_counter = 50)
diff --git a/testdata/result/statement/create_table.sql.txt b/testdata/result/statement/create_table.sql.txt
index 4622af8..a96ca0e 100644
--- a/testdata/result/statement/create_table.sql.txt
+++ b/testdata/result/statement/create_table.sql.txt
@@ -14,9 +14,10 @@ create table foo (
 
 --- AST
 &ast.CreateTable{
-  Create: 0,
-  Rparen: 447,
-  Name:   &ast.Ident{
+  Create:      0,
+  Rparen:      447,
+  IfNotExists: false,
+  Name:        &ast.Ident{
     NamePos: 13,
     NameEnd: 16,
     Name:    "foo",
diff --git a/testdata/result/statement/create_table_cluster.sql.txt b/testdata/result/statement/create_table_cluster.sql.txt
index 7569729..8eb83c3 100644
--- a/testdata/result/statement/create_table_cluster.sql.txt
+++ b/testdata/result/statement/create_table_cluster.sql.txt
@@ -7,9 +7,10 @@ create table foo (
 
 --- AST
 &ast.CreateTable{
-  Create: 0,
-  Rparen: 59,
-  Name:   &ast.Ident{
+  Create:      0,
+  Rparen:      59,
+  IfNotExists: false,
+  Name:        &ast.Ident{
     NamePos: 13,
     NameEnd: 16,
     Name:    "foo",
diff --git a/testdata/result/statement/create_table_cluster_and_row_deletion_policy.sql.txt b/testdata/result/statement/create_table_cluster_and_row_deletion_policy.sql.txt
index 7f6e638..26aa3fb 100644
--- a/testdata/result/statement/create_table_cluster_and_row_deletion_policy.sql.txt
+++ b/testdata/result/statement/create_table_cluster_and_row_deletion_policy.sql.txt
@@ -9,9 +9,10 @@ create table foo (
 
 --- AST
 &ast.CreateTable{
-  Create: 0,
-  Rparen: 77,
-  Name:   &ast.Ident{
+  Create:      0,
+  Rparen:      77,
+  IfNotExists: false,
+  Name:        &ast.Ident{
     NamePos: 13,
     NameEnd: 16,
     Name:    "foo",
diff --git a/testdata/result/statement/create_table_cluster_on_delete_no_action.sql.txt b/testdata/result/statement/create_table_cluster_on_delete_no_action.sql.txt
index 8802ba7..c2caa5c 100644
--- a/testdata/result/statement/create_table_cluster_on_delete_no_action.sql.txt
+++ b/testdata/result/statement/create_table_cluster_on_delete_no_action.sql.txt
@@ -6,9 +6,10 @@ create table foo (
              on delete no action
 --- AST
 &ast.CreateTable{
-  Create: 0,
-  Rparen: 50,
-  Name:   &ast.Ident{
+  Create:      0,
+  Rparen:      50,
+  IfNotExists: false,
+  Name:        &ast.Ident{
     NamePos: 13,
     NameEnd: 16,
     Name:    "foo",
diff --git a/testdata/result/statement/create_table_cluster_set_on_delete.sql.txt b/testdata/result/statement/create_table_cluster_set_on_delete.sql.txt
index 093b5cc..4de2bcc 100644
--- a/testdata/result/statement/create_table_cluster_set_on_delete.sql.txt
+++ b/testdata/result/statement/create_table_cluster_set_on_delete.sql.txt
@@ -6,9 +6,10 @@ create table foo (
              on delete cascade
 --- AST
 &ast.CreateTable{
-  Create: 0,
-  Rparen: 49,
-  Name:   &ast.Ident{
+  Create:      0,
+  Rparen:      49,
+  IfNotExists: false,
+  Name:        &ast.Ident{
     NamePos: 13,
     NameEnd: 16,
     Name:    "foo",
diff --git a/testdata/result/statement/create_table_if_not_exists.sql.txt b/testdata/result/statement/create_table_if_not_exists.sql.txt
new file mode 100644
index 0000000..1677b5c
--- /dev/null
+++ b/testdata/result/statement/create_table_if_not_exists.sql.txt
@@ -0,0 +1,77 @@
+--- create_table_if_not_exists.sql
+create table if not exists foo (
+  foo int64,
+  bar float64 not null,
+) primary key (foo, bar)
+
+--- AST
+&ast.CreateTable{
+  Create:      0,
+  Rparen:      93,
+  IfNotExists: true,
+  Name:        &ast.Ident{
+    NamePos: 27,
+    NameEnd: 30,
+    Name:    "foo",
+  },
+  Columns: []*ast.ColumnDef{
+    &ast.ColumnDef{
+      Null: -1,
+      Name: &ast.Ident{
+        NamePos: 35,
+        NameEnd: 38,
+        Name:    "foo",
+      },
+      Type: &ast.ScalarSchemaType{
+        NamePos: 39,
+        Name:    "INT64",
+      },
+      NotNull:       false,
+      DefaultExpr:   (*ast.ColumnDefaultExpr)(nil),
+      GeneratedExpr: (*ast.GeneratedColumnExpr)(nil),
+      Options:       (*ast.ColumnDefOptions)(nil),
+    },
+    &ast.ColumnDef{
+      Null: 64,
+      Name: &ast.Ident{
+        NamePos: 48,
+        NameEnd: 51,
+        Name:    "bar",
+      },
+      Type: &ast.ScalarSchemaType{
+        NamePos: 52,
+        Name:    "FLOAT64",
+      },
+      NotNull:       true,
+      DefaultExpr:   (*ast.ColumnDefaultExpr)(nil),
+      GeneratedExpr: (*ast.GeneratedColumnExpr)(nil),
+      Options:       (*ast.ColumnDefOptions)(nil),
+    },
+  },
+  TableConstraints: []*ast.TableConstraint(nil),
+  PrimaryKeys:      []*ast.IndexKey{
+    &ast.IndexKey{
+      DirPos: -1,
+      Name:   &ast.Ident{
+        NamePos: 85,
+        NameEnd: 88,
+        Name:    "foo",
+      },
+      Dir: "",
+    },
+    &ast.IndexKey{
+      DirPos: -1,
+      Name:   &ast.Ident{
+        NamePos: 90,
+        NameEnd: 93,
+        Name:    "bar",
+      },
+      Dir: "",
+    },
+  },
+  Cluster:           (*ast.Cluster)(nil),
+  RowDeletionPolicy: (*ast.CreateRowDeletionPolicy)(nil),
+}
+
+--- SQL
+CREATE TABLE IF NOT EXISTS foo (foo INT64, bar FLOAT64 NOT NULL) PRIMARY KEY (foo, bar)
diff --git a/testdata/result/statement/create_table_row_deletion_policy.sql.txt b/testdata/result/statement/create_table_row_deletion_policy.sql.txt
index fe72eed..4a49748 100644
--- a/testdata/result/statement/create_table_row_deletion_policy.sql.txt
+++ b/testdata/result/statement/create_table_row_deletion_policy.sql.txt
@@ -8,9 +8,10 @@ create table foo (
 
 --- AST
 &ast.CreateTable{
-  Create: 0,
-  Rparen: 77,
-  Name:   &ast.Ident{
+  Create:      0,
+  Rparen:      77,
+  IfNotExists: false,
+  Name:        &ast.Ident{
     NamePos: 13,
     NameEnd: 16,
     Name:    "foo",
diff --git a/testdata/result/statement/create_table_trailing_comma.sql.txt b/testdata/result/statement/create_table_trailing_comma.sql.txt
index a7a8aa8..0ff16b9 100644
--- a/testdata/result/statement/create_table_trailing_comma.sql.txt
+++ b/testdata/result/statement/create_table_trailing_comma.sql.txt
@@ -9,9 +9,10 @@ create table foo (
 
 --- AST
 &ast.CreateTable{
-  Create: 0,
-  Rparen: 83,
-  Name:   &ast.Ident{
+  Create:      0,
+  Rparen:      83,
+  IfNotExists: false,
+  Name:        &ast.Ident{
     NamePos: 13,
     NameEnd: 16,
     Name:    "foo",
diff --git a/testdata/result/statement/create_table_types.sql.txt b/testdata/result/statement/create_table_types.sql.txt
index 71d7773..349c0f4 100644
--- a/testdata/result/statement/create_table_types.sql.txt
+++ b/testdata/result/statement/create_table_types.sql.txt
@@ -15,9 +15,10 @@ create table types (
 
 --- AST
 &ast.CreateTable{
-  Create: 0,
-  Rparen: 212,
-  Name:   &ast.Ident{
+  Create:      0,
+  Rparen:      212,
+  IfNotExists: false,
+  Name:        &ast.Ident{
     NamePos: 13,
     NameEnd: 18,
     Name:    "types",