From c64edc6fe97cdffb485d3ce5ee2bab8b892ea20e Mon Sep 17 00:00:00 2001 From: Andrew Farries Date: Thu, 19 Dec 2024 13:35:19 +0000 Subject: [PATCH] Handle `DEFERRABLE` and `NOT DEFERRABLE` constraints in `CREATE TABLE` statements (#556) Handle `DEFERRABLE` and `NOT DEFERRABLE` constraints in `CREATE TABLE` statements `NOT DEFERRABLE` constraints are the default, so add testcases to ensure that constraints with this modifier are converted to `OpCreateTable` operations. `DEFERRABLE` constraints and the `INITIALLY IMMEDIATE` and `INITIALLY DEFERRED` modifiers are not supported, so fall back to raw SQL operations when these options are present. --- pkg/sql2pgroll/create_table.go | 16 ++++++++++ pkg/sql2pgroll/create_table_test.go | 45 ++++++++++++++++++++++++++++- 2 files changed, 60 insertions(+), 1 deletion(-) diff --git a/pkg/sql2pgroll/create_table.go b/pkg/sql2pgroll/create_table.go index 40e77541..3c055963 100644 --- a/pkg/sql2pgroll/create_table.go +++ b/pkg/sql2pgroll/create_table.go @@ -139,12 +139,28 @@ func convertColumnDef(tableName string, col *pgq.ColumnDef) (*migrations.Column, if foreignKey == nil { return nil, nil } + case pgq.ConstrType_CONSTR_ATTR_NOT_DEFERRABLE: + // NOT DEFERRABLE constraints are the default and are supported, but no + // extra annotation is needed + continue case pgq.ConstrType_CONSTR_GENERATED: // Generated columns are not supported return nil, nil case pgq.ConstrType_CONSTR_IDENTITY: // Identity columns are not supported return nil, nil + case pgq.ConstrType_CONSTR_ATTR_DEFERRABLE: + // Deferrable constraints are not supported + return nil, nil + case pgq.ConstrType_CONSTR_ATTR_IMMEDIATE: + // Initially immediate deferred constraints are not supported + return nil, nil + case pgq.ConstrType_CONSTR_ATTR_DEFERRED: + // Initially deferred deferred constraints are not supported + return nil, nil + default: + // Any other type of constraint is not supported + return nil, nil } } diff --git a/pkg/sql2pgroll/create_table_test.go b/pkg/sql2pgroll/create_table_test.go index 5841e107..31b6a914 100644 --- a/pkg/sql2pgroll/create_table_test.go +++ b/pkg/sql2pgroll/create_table_test.go @@ -36,10 +36,18 @@ func TestConvertCreateTableStatements(t *testing.T) { sql: "CREATE TABLE foo(a int UNIQUE)", expectedOp: expect.CreateTableOp5, }, + { + sql: "CREATE TABLE foo(a int UNIQUE NOT DEFERRABLE)", + expectedOp: expect.CreateTableOp5, + }, { sql: "CREATE TABLE foo(a int PRIMARY KEY)", expectedOp: expect.CreateTableOp6, }, + { + sql: "CREATE TABLE foo(a int PRIMARY KEY NOT DEFERRABLE)", + expectedOp: expect.CreateTableOp6, + }, { sql: "CREATE TABLE foo(a int CHECK (a > 0))", expectedOp: expect.CreateTableOp10, @@ -49,33 +57,57 @@ func TestConvertCreateTableStatements(t *testing.T) { expectedOp: expect.CreateTableOp11, }, { - sql: "CREATE TABLE foo(a int REFERENCES bar(b))", + sql: "CREATE TABLE foo(a int REFERENCES bar(b) NOT DEFERRABLE)", expectedOp: expect.CreateTableOp12, }, { sql: "CREATE TABLE foo(a int REFERENCES bar(b) ON UPDATE NO ACTION)", expectedOp: expect.CreateTableOp12, }, + { + sql: "CREATE TABLE foo(a int REFERENCES bar(b) ON UPDATE NO ACTION NOT DEFERRABLE)", + expectedOp: expect.CreateTableOp12, + }, { sql: "CREATE TABLE foo(a int REFERENCES bar(b) ON DELETE NO ACTION)", expectedOp: expect.CreateTableOp12, }, + { + sql: "CREATE TABLE foo(a int REFERENCES bar(b) ON DELETE NO ACTION NOT DEFERRABLE)", + expectedOp: expect.CreateTableOp12, + }, { sql: "CREATE TABLE foo(a int REFERENCES bar(b) ON DELETE RESTRICT)", expectedOp: expect.CreateTableOp13, }, + { + sql: "CREATE TABLE foo(a int REFERENCES bar(b) ON DELETE RESTRICT NOT DEFERRABLE)", + expectedOp: expect.CreateTableOp13, + }, { sql: "CREATE TABLE foo(a int REFERENCES bar(b) ON DELETE SET NULL)", expectedOp: expect.CreateTableOp14, }, + { + sql: "CREATE TABLE foo(a int REFERENCES bar(b) ON DELETE SET NULL NOT DEFERRABLE)", + expectedOp: expect.CreateTableOp14, + }, { sql: "CREATE TABLE foo(a int REFERENCES bar(b) ON DELETE SET DEFAULT)", expectedOp: expect.CreateTableOp15, }, + { + sql: "CREATE TABLE foo(a int REFERENCES bar(b) ON DELETE SET DEFAULT NOT DEFERRABLE)", + expectedOp: expect.CreateTableOp15, + }, { sql: "CREATE TABLE foo(a int REFERENCES bar(b) ON DELETE CASCADE)", expectedOp: expect.CreateTableOp16, }, + { + sql: "CREATE TABLE foo(a int REFERENCES bar(b) ON DELETE CASCADE NOT DEFERRABLE)", + expectedOp: expect.CreateTableOp16, + }, { sql: "CREATE TABLE foo(a varchar(255))", expectedOp: expect.CreateTableOp3, @@ -203,6 +235,17 @@ func TestUnconvertableCreateTableStatements(t *testing.T) { // Generated columns are not supported "CREATE TABLE foo(a int GENERATED ALWAYS AS (1) STORED)", "CREATE TABLE foo(a int GENERATED ALWAYS AS IDENTITY)", + + // Deferrable constraints are not supported + "CREATE TABLE foo(a int UNIQUE DEFERRABLE)", + "CREATE TABLE foo(a int PRIMARY KEY DEFERRABLE)", + "CREATE TABLE foo(a int REFERENCES bar(b) DEFERRABLE)", + "CREATE TABLE foo(a int UNIQUE DEFERRABLE INITIALLY IMMEDIATE)", + "CREATE TABLE foo(a int PRIMARY KEY DEFERRABLE INITIALLY IMMEDIATE)", + "CREATE TABLE foo(a int REFERENCES bar(b) DEFERRABLE INITIALLY IMMEDIATE)", + "CREATE TABLE foo(a int UNIQUE DEFERRABLE INITIALLY DEFERRED)", + "CREATE TABLE foo(a int PRIMARY KEY DEFERRABLE INITIALLY DEFERRED)", + "CREATE TABLE foo(a int REFERENCES bar(b) DEFERRABLE INITIALLY DEFERRED)", } for _, sql := range tests {