Repository: cloudspannerecosystem/memefish
Pull request #294 (2025-03-22 08:57:44)

Support unenforced interleave
This PR implements `INTERLEAVE IN` without `PARENT` in `CREATE TABLE`.
It is implemented as `Enforced bool` flag. ~optional `token.Pos` of `PARENT`.~ 

## Related Issues

- part of #293 

apstndb: I feel `Enforce bool` is better than `token.Pos`.

Patch:
diff --git a/ast/ast.go b/ast/ast.go
index dcc95ca..be15e4f 100644
--- a/ast/ast.go
+++ b/ast/ast.go
@@ -2660,9 +2660,9 @@ type IndexKey struct {
 	Dir  Direction // optional
 }
 
-// Cluster is INTERLEAVE IN PARENT clause in CREATE TABLE.
+// Cluster is INTERLEAVE IN [PARENT] clause in CREATE TABLE.
 //
-//	, INTERLEAVE IN PARENT {{.TableName | sql}} {{.OnDelete}}
+//	, INTERLEAVE IN {{if .Enforced}}PARENT{{end}} {{.TableName | sql}} {{.OnDelete}}
 type Cluster struct {
 	// pos = Comma
 	// end = OnDeleteEnd || TableName.end
@@ -2671,6 +2671,7 @@ type Cluster struct {
 	OnDeleteEnd token.Pos // end position of ON DELETE clause
 
 	TableName *Path
+	Enforced  bool           // true when PARENT is present
 	OnDelete  OnDeleteAction // optional
 }
 
diff --git a/ast/sql.go b/ast/sql.go
index 18b5232..5e45c7a 100644
--- a/ast/sql.go
+++ b/ast/sql.go
@@ -894,7 +894,7 @@ func (i *IndexKey) SQL() string {
 }
 
 func (c *Cluster) SQL() string {
-	return ",\n" + indent + "INTERLEAVE IN PARENT " + c.TableName.SQL() +
+	return ",\n" + indent + "INTERLEAVE IN " + strOpt(c.Enforced, "PARENT ") + c.TableName.SQL() +
 		strOpt(c.OnDelete != "", " "+string(c.OnDelete))
 }
 
diff --git a/parser.go b/parser.go
index 68d99c6..b3a46b9 100644
--- a/parser.go
+++ b/parser.go
@@ -3524,7 +3524,13 @@ func (p *Parser) tryParseCluster() *ast.Cluster {
 	}
 	p.nextToken()
 	p.expect("IN")
-	p.expectKeywordLike("PARENT")
+
+	var enforced bool
+	if p.Token.IsKeywordLike("PARENT") {
+		p.nextToken()
+		enforced = true
+	}
+
 	name := p.parsePath()
 
 	onDelete, onDeleteEnd := p.tryParseOnDeleteAction()
@@ -3533,6 +3539,7 @@ func (p *Parser) tryParseCluster() *ast.Cluster {
 		Comma:       pos,
 		OnDeleteEnd: onDeleteEnd,
 		TableName:   name,
+		Enforced:    enforced,
 		OnDelete:    onDelete,
 	}
 }


Test patch:
diff --git a/testdata/input/ddl/create_table_cluster_without_parent.sql b/testdata/input/ddl/create_table_cluster_without_parent.sql
new file mode 100644
index 0000000..c4cb0d1
--- /dev/null
+++ b/testdata/input/ddl/create_table_cluster_without_parent.sql
@@ -0,0 +1,5 @@
+create table foo (
+  foo int64,
+  bar int64
+) primary key (foo, bar),
+  interleave in foobar
diff --git a/testdata/result/ddl/create_table_cluster.sql.txt b/testdata/result/ddl/create_table_cluster.sql.txt
index b49f28c..b8b60ea 100644
--- a/testdata/result/ddl/create_table_cluster.sql.txt
+++ b/testdata/result/ddl/create_table_cluster.sql.txt
@@ -60,6 +60,7 @@ create table foo (
         },
       },
     },
+    Enforced: true,
   },
 }
 
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 54b627b..5463648 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
@@ -76,6 +76,7 @@ create table foo (
         },
       },
     },
+    Enforced: true,
   },
   RowDeletionPolicy: &ast.CreateRowDeletionPolicy{
     Comma:             109,
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 afc8cc0..7f41cbd 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
@@ -55,6 +55,7 @@ create table foo (
         },
       },
     },
+    Enforced: true,
     OnDelete: "ON DELETE NO ACTION",
   },
 }
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 f3784a4..1f6a7fe 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
@@ -55,6 +55,7 @@ create table foo (
         },
       },
     },
+    Enforced: true,
     OnDelete: "ON DELETE CASCADE",
   },
 }
diff --git a/testdata/result/ddl/create_table_cluster_without_parent.sql.txt b/testdata/result/ddl/create_table_cluster_without_parent.sql.txt
new file mode 100644
index 0000000..ed3049e
--- /dev/null
+++ b/testdata/result/ddl/create_table_cluster_without_parent.sql.txt
@@ -0,0 +1,89 @@
+--- create_table_cluster_without_parent.sql
+create table foo (
+  foo int64,
+  bar int64
+) primary key (foo, bar),
+  interleave in foobar
+
+--- AST
+&ast.CreateTable{
+  Rparen:           44,
+  PrimaryKeyRparen: 67,
+  Name:             &ast.Path{
+    Idents: []*ast.Ident{
+      &ast.Ident{
+        NamePos: 13,
+        NameEnd: 16,
+        Name:    "foo",
+      },
+    },
+  },
+  Columns: []*ast.ColumnDef{
+    &ast.ColumnDef{
+      Null: -1,
+      Key:  -1,
+      Name: &ast.Ident{
+        NamePos: 21,
+        NameEnd: 24,
+        Name:    "foo",
+      },
+      Type: &ast.ScalarSchemaType{
+        NamePos: 25,
+        Name:    "INT64",
+      },
+      Hidden: -1,
+    },
+    &ast.ColumnDef{
+      Null: -1,
+      Key:  -1,
+      Name: &ast.Ident{
+        NamePos: 34,
+        NameEnd: 37,
+        Name:    "bar",
+      },
+      Type: &ast.ScalarSchemaType{
+        NamePos: 38,
+        Name:    "INT64",
+      },
+      Hidden: -1,
+    },
+  },
+  PrimaryKeys: []*ast.IndexKey{
+    &ast.IndexKey{
+      DirPos: -1,
+      Name:   &ast.Ident{
+        NamePos: 59,
+        NameEnd: 62,
+        Name:    "foo",
+      },
+    },
+    &ast.IndexKey{
+      DirPos: -1,
+      Name:   &ast.Ident{
+        NamePos: 64,
+        NameEnd: 67,
+        Name:    "bar",
+      },
+    },
+  },
+  Cluster: &ast.Cluster{
+    Comma:       68,
+    OnDeleteEnd: -1,
+    TableName:   &ast.Path{
+      Idents: []*ast.Ident{
+        &ast.Ident{
+          NamePos: 86,
+          NameEnd: 92,
+          Name:    "foobar",
+        },
+      },
+    },
+  },
+}
+
+--- SQL
+CREATE TABLE foo (
+  foo INT64,
+  bar INT64
+) PRIMARY KEY (foo, bar),
+  INTERLEAVE IN foobar
diff --git a/testdata/result/ddl/create_table_for_format_test.sql.txt b/testdata/result/ddl/create_table_for_format_test.sql.txt
index 32b978d..4429502 100644
--- a/testdata/result/ddl/create_table_for_format_test.sql.txt
+++ b/testdata/result/ddl/create_table_for_format_test.sql.txt
@@ -332,6 +332,7 @@ create table if not exists foo (
         },
       },
     },
+    Enforced: true,
   },
   RowDeletionPolicy: &ast.CreateRowDeletionPolicy{
     Comma:             493,
diff --git a/testdata/result/ddl/named_schemas_create_table_interleave.sql.txt b/testdata/result/ddl/named_schemas_create_table_interleave.sql.txt
index cb68b1f..d9b3cb5 100644
--- a/testdata/result/ddl/named_schemas_create_table_interleave.sql.txt
+++ b/testdata/result/ddl/named_schemas_create_table_interleave.sql.txt
@@ -106,6 +106,7 @@ CREATE TABLE sch1.Albums (
         },
       },
     },
+    Enforced: true,
     OnDelete: "ON DELETE CASCADE",
   },
 }
diff --git a/testdata/result/statement/create_table_cluster.sql.txt b/testdata/result/statement/create_table_cluster.sql.txt
index b49f28c..b8b60ea 100644
--- a/testdata/result/statement/create_table_cluster.sql.txt
+++ b/testdata/result/statement/create_table_cluster.sql.txt
@@ -60,6 +60,7 @@ create table foo (
         },
       },
     },
+    Enforced: true,
   },
 }
 
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 54b627b..5463648 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
@@ -76,6 +76,7 @@ create table foo (
         },
       },
     },
+    Enforced: true,
   },
   RowDeletionPolicy: &ast.CreateRowDeletionPolicy{
     Comma:             109,
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 afc8cc0..7f41cbd 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
@@ -55,6 +55,7 @@ create table foo (
         },
       },
     },
+    Enforced: true,
     OnDelete: "ON DELETE NO ACTION",
   },
 }
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 f3784a4..1f6a7fe 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
@@ -55,6 +55,7 @@ create table foo (
         },
       },
     },
+    Enforced: true,
     OnDelete: "ON DELETE CASCADE",
   },
 }
diff --git a/testdata/result/statement/create_table_cluster_without_parent.sql.txt b/testdata/result/statement/create_table_cluster_without_parent.sql.txt
new file mode 100644
index 0000000..ed3049e
--- /dev/null
+++ b/testdata/result/statement/create_table_cluster_without_parent.sql.txt
@@ -0,0 +1,89 @@
+--- create_table_cluster_without_parent.sql
+create table foo (
+  foo int64,
+  bar int64
+) primary key (foo, bar),
+  interleave in foobar
+
+--- AST
+&ast.CreateTable{
+  Rparen:           44,
+  PrimaryKeyRparen: 67,
+  Name:             &ast.Path{
+    Idents: []*ast.Ident{
+      &ast.Ident{
+        NamePos: 13,
+        NameEnd: 16,
+        Name:    "foo",
+      },
+    },
+  },
+  Columns: []*ast.ColumnDef{
+    &ast.ColumnDef{
+      Null: -1,
+      Key:  -1,
+      Name: &ast.Ident{
+        NamePos: 21,
+        NameEnd: 24,
+        Name:    "foo",
+      },
+      Type: &ast.ScalarSchemaType{
+        NamePos: 25,
+        Name:    "INT64",
+      },
+      Hidden: -1,
+    },
+    &ast.ColumnDef{
+      Null: -1,
+      Key:  -1,
+      Name: &ast.Ident{
+        NamePos: 34,
+        NameEnd: 37,
+        Name:    "bar",
+      },
+      Type: &ast.ScalarSchemaType{
+        NamePos: 38,
+        Name:    "INT64",
+      },
+      Hidden: -1,
+    },
+  },
+  PrimaryKeys: []*ast.IndexKey{
+    &ast.IndexKey{
+      DirPos: -1,
+      Name:   &ast.Ident{
+        NamePos: 59,
+        NameEnd: 62,
+        Name:    "foo",
+      },
+    },
+    &ast.IndexKey{
+      DirPos: -1,
+      Name:   &ast.Ident{
+        NamePos: 64,
+        NameEnd: 67,
+        Name:    "bar",
+      },
+    },
+  },
+  Cluster: &ast.Cluster{
+    Comma:       68,
+    OnDeleteEnd: -1,
+    TableName:   &ast.Path{
+      Idents: []*ast.Ident{
+        &ast.Ident{
+          NamePos: 86,
+          NameEnd: 92,
+          Name:    "foobar",
+        },
+      },
+    },
+  },
+}
+
+--- SQL
+CREATE TABLE foo (
+  foo INT64,
+  bar INT64
+) PRIMARY KEY (foo, bar),
+  INTERLEAVE IN foobar
diff --git a/testdata/result/statement/create_table_for_format_test.sql.txt b/testdata/result/statement/create_table_for_format_test.sql.txt
index 32b978d..4429502 100644
--- a/testdata/result/statement/create_table_for_format_test.sql.txt
+++ b/testdata/result/statement/create_table_for_format_test.sql.txt
@@ -332,6 +332,7 @@ create table if not exists foo (
         },
       },
     },
+    Enforced: true,
   },
   RowDeletionPolicy: &ast.CreateRowDeletionPolicy{
     Comma:             493,
diff --git a/testdata/result/statement/named_schemas_create_table_interleave.sql.txt b/testdata/result/statement/named_schemas_create_table_interleave.sql.txt
index cb68b1f..d9b3cb5 100644
--- a/testdata/result/statement/named_schemas_create_table_interleave.sql.txt
+++ b/testdata/result/statement/named_schemas_create_table_interleave.sql.txt
@@ -106,6 +106,7 @@ CREATE TABLE sch1.Albums (
         },
       },
     },
+    Enforced: true,
     OnDelete: "ON DELETE CASCADE",
   },
 }