From 5b040171ab55c7aaca765850c440da7c879d810f Mon Sep 17 00:00:00 2001 From: Andrew Farries Date: Fri, 20 Dec 2024 07:38:16 +0000 Subject: [PATCH] Allow `INITIALLY IMMEDIATE` constraints Convert `INITIALLY IMMEDIATE` constraints appearing in `CREATE TABLE` column constraints to `OpCreateTable` operations rather than raw SQL. The documentation is a little misleading, as it makes it sound like `INITIALLY IMMEDIATE` implies that the constraint is `DEFERRABLE`, but in practice it looks like `INITIALLY IMMEDIATE` without `DEFERRABLE` is a no-op. Documentation: https://www.postgresql.org/docs/current/sql-createtable.html#SQL-CREATETABLE-PARMS-INITIALLY In practice: ```sql CREATE TABLE foo(a int PRIMARY KEY GENERATED ALWAYS AS IDENTITY) -- Specify an INITIALLY IMMEDIATE constraint without DEFERRABLE ALTER TABLE foo ADD COLUMN bar int UNIQUE INITIALLY IMMEDIATE -- Query the catalog to see the constraint SELECT conname AS constraint_name, condeferrable AS is_deferrable, condeferred AS is_deferred FROM pg_constraint WHERE conrelid = 'foo'::regclass; ``` Result: ``` +-----------------+---------------+-------------+ | constraint_name | is_deferrable | is_deferred | |-----------------+---------------+-------------| | foo_pkey | False | False | | foo_bar_key | False | False | +-----------------+---------------+-------------+ ``` ie, the `UNIQUE` constraint added to `bar` is not `DEFERRABLE` or `DEFERRED`. In contrast, if the column `bar` is defined: ```sql ALTER TABLE foo ADD COLUMN bar int UNIQUE DEFERRABLE INITIALLY IMMEDIATE ``` Then the same catalog query shows: ``` +-----------------+---------------+-------------+ | constraint_name | is_deferrable | is_deferred | |-----------------+---------------+-------------| | foo_pkey | False | False | | foo_bar_key | True | False | +-----------------+---------------+-------------+ ``` ie, the constraint is `DEFERRABLE`. So specifying `INITIALLY IMMEDIATE` without `DEFERRABLE` is a no-op. --- pkg/sql2pgroll/create_table.go | 11 +++++------ pkg/sql2pgroll/create_table_test.go | 12 ++++++++++++ 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/pkg/sql2pgroll/create_table.go b/pkg/sql2pgroll/create_table.go index 68a646be..42be400c 100644 --- a/pkg/sql2pgroll/create_table.go +++ b/pkg/sql2pgroll/create_table.go @@ -150,9 +150,11 @@ 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 + case + pgq.ConstrType_CONSTR_ATTR_NOT_DEFERRABLE, + pgq.ConstrType_CONSTR_ATTR_IMMEDIATE: + // NOT DEFERRABLE and INITIALLY IMMEDIATE constraints are the default and + // are supported, but no extra annotation is needed continue case pgq.ConstrType_CONSTR_GENERATED: // Generated columns are not supported @@ -163,9 +165,6 @@ func convertColumnDef(tableName string, col *pgq.ColumnDef) (*migrations.Column, 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 diff --git a/pkg/sql2pgroll/create_table_test.go b/pkg/sql2pgroll/create_table_test.go index 591fb50c..5538ed8b 100644 --- a/pkg/sql2pgroll/create_table_test.go +++ b/pkg/sql2pgroll/create_table_test.go @@ -44,6 +44,10 @@ func TestConvertCreateTableStatements(t *testing.T) { sql: "CREATE TABLE foo(a int UNIQUE NOT DEFERRABLE)", expectedOp: expect.CreateTableOp5, }, + { + sql: "CREATE TABLE foo(a int UNIQUE INITIALLY IMMEDIATE)", + expectedOp: expect.CreateTableOp5, + }, { sql: "CREATE TABLE foo(a int PRIMARY KEY)", expectedOp: expect.CreateTableOp6, @@ -52,6 +56,10 @@ func TestConvertCreateTableStatements(t *testing.T) { sql: "CREATE TABLE foo(a int PRIMARY KEY NOT DEFERRABLE)", expectedOp: expect.CreateTableOp6, }, + { + sql: "CREATE TABLE foo(a int PRIMARY KEY INITIALLY IMMEDIATE)", + expectedOp: expect.CreateTableOp6, + }, { sql: "CREATE TABLE foo(a int CHECK (a > 0))", expectedOp: expect.CreateTableOp10, @@ -76,6 +84,10 @@ func TestConvertCreateTableStatements(t *testing.T) { sql: "CREATE TABLE foo(a int REFERENCES bar(b) NOT DEFERRABLE)", expectedOp: expect.CreateTableOp12, }, + { + sql: "CREATE TABLE foo(a int REFERENCES bar(b) INITIALLY IMMEDIATE)", + expectedOp: expect.CreateTableOp12, + }, { sql: "CREATE TABLE foo(a int REFERENCES bar(b) ON UPDATE NO ACTION)", expectedOp: expect.CreateTableOp12,