]> git.ipfire.org Git - thirdparty/postgresql.git/commitdiff
Execute foreign key constraints in CREATE SCHEMA at the end.
authorTom Lane <tgl@sss.pgh.pa.us>
Mon, 6 Apr 2026 18:52:28 +0000 (14:52 -0400)
committerTom Lane <tgl@sss.pgh.pa.us>
Mon, 6 Apr 2026 19:16:25 +0000 (15:16 -0400)
The previous patch simplified CREATE SCHEMA's behavior to "execute all
subcommands in the order they are written".  However, that's a bit too
simple, as the spec clearly requires forward references in foreign key
constraint clauses to work, see feature F311-01.  (Most other SQL
implementations seem to read more into the spec than that, but it's
not clear that there's justification for more in the text, and this is
the only case that doesn't introduce unresolvable issues.)  We never
implemented that before, but let's do so now.

To fix it, transform FOREIGN KEY clauses into ALTER TABLE ... ADD
FOREIGN KEY commands and append them to the end of the CREATE SCHEMA's
subcommand list.  This works because the foreign key constraints are
independent and don't affect any other DDL that might be in CREATE
SCHEMA.  For simplicity, we do this for all FOREIGN KEY clauses even
if they would have worked where they were.

Author: Jian He <jian.universality@gmail.com>
Co-authored-by: Tom Lane <tgl@sss.pgh.pa.us>
Discussion: https://postgr.es/m/1075425.1732993688@sss.pgh.pa.us

doc/src/sgml/ref/create_schema.sgml
src/backend/parser/parse_utilcmd.c
src/test/modules/test_ddl_deparse/expected/create_schema.out
src/test/modules/test_ddl_deparse/sql/create_schema.sql
src/test/regress/expected/create_schema.out
src/test/regress/expected/event_trigger.out
src/test/regress/sql/create_schema.sql

index 9e6f0e243396b081771eced5159b5b011050e156..96bc496e7777f18542721830794dfcdbd87f6743 100644 (file)
@@ -135,6 +135,9 @@ CREATE SCHEMA IF NOT EXISTS AUTHORIZATION <replaceable class="parameter">role_sp
   <para>
    The <replaceable class="parameter">schema_element</replaceable>
    subcommands, if any, are executed in the order they are written.
+   An exception is that foreign key constraint clauses in <command>CREATE
+   TABLE</command> subcommands are postponed and added at the end.
+   This allows circular foreign key references, which are sometimes useful.
   </para>
  </refsect1>
 
index 151d97312732eaabb5d01c2ea8021ea85d9e3b44..b0c3407b23d464d870b612955a63e29d04f9494c 100644 (file)
@@ -122,11 +122,14 @@ static void transformFKConstraints(CreateStmtContext *cxt,
                                                                   bool isAddConstraint);
 static void transformCheckConstraints(CreateStmtContext *cxt,
                                                                          bool skipValidation);
-static void transformConstraintAttrs(CreateStmtContext *cxt,
+static void transformConstraintAttrs(ParseState *pstate,
                                                                         List *constraintList);
 static void transformColumnType(CreateStmtContext *cxt, ColumnDef *column);
 static void checkSchemaNameRV(ParseState *pstate, const char *context_schema,
                                                          RangeVar *relation);
+static CreateStmt *transformCreateSchemaCreateTable(ParseState *pstate,
+                                                                                                       CreateStmt *stmt,
+                                                                                                       List **fk_elements);
 static void transformPartitionCmd(CreateStmtContext *cxt, PartitionBoundSpec *bound);
 static List *transformPartitionRangeBounds(ParseState *pstate, List *blist,
                                                                                   Relation parent);
@@ -693,7 +696,7 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
        }
 
        /* Process column constraints, if any... */
-       transformConstraintAttrs(cxt, column->constraints);
+       transformConstraintAttrs(cxt->pstate, column->constraints);
 
        /*
         * First, scan the column's constraints to see if a not-null constraint
@@ -4194,9 +4197,12 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
  * NOTE: currently, attributes are only supported for FOREIGN KEY, UNIQUE,
  * EXCLUSION, and PRIMARY KEY constraints, but someday they ought to be
  * supported for other constraint types.
+ *
+ * NOTE: this must be idempotent in non-error cases; see
+ * transformCreateSchemaCreateTable.
  */
 static void
-transformConstraintAttrs(CreateStmtContext *cxt, List *constraintList)
+transformConstraintAttrs(ParseState *pstate, List *constraintList)
 {
        Constraint *lastprimarycon = NULL;
        bool            saw_deferrability = false;
@@ -4225,12 +4231,12 @@ transformConstraintAttrs(CreateStmtContext *cxt, List *constraintList)
                                        ereport(ERROR,
                                                        (errcode(ERRCODE_SYNTAX_ERROR),
                                                         errmsg("misplaced DEFERRABLE clause"),
-                                                        parser_errposition(cxt->pstate, con->location)));
+                                                        parser_errposition(pstate, con->location)));
                                if (saw_deferrability)
                                        ereport(ERROR,
                                                        (errcode(ERRCODE_SYNTAX_ERROR),
                                                         errmsg("multiple DEFERRABLE/NOT DEFERRABLE clauses not allowed"),
-                                                        parser_errposition(cxt->pstate, con->location)));
+                                                        parser_errposition(pstate, con->location)));
                                saw_deferrability = true;
                                lastprimarycon->deferrable = true;
                                break;
@@ -4240,12 +4246,12 @@ transformConstraintAttrs(CreateStmtContext *cxt, List *constraintList)
                                        ereport(ERROR,
                                                        (errcode(ERRCODE_SYNTAX_ERROR),
                                                         errmsg("misplaced NOT DEFERRABLE clause"),
-                                                        parser_errposition(cxt->pstate, con->location)));
+                                                        parser_errposition(pstate, con->location)));
                                if (saw_deferrability)
                                        ereport(ERROR,
                                                        (errcode(ERRCODE_SYNTAX_ERROR),
                                                         errmsg("multiple DEFERRABLE/NOT DEFERRABLE clauses not allowed"),
-                                                        parser_errposition(cxt->pstate, con->location)));
+                                                        parser_errposition(pstate, con->location)));
                                saw_deferrability = true;
                                lastprimarycon->deferrable = false;
                                if (saw_initially &&
@@ -4253,7 +4259,7 @@ transformConstraintAttrs(CreateStmtContext *cxt, List *constraintList)
                                        ereport(ERROR,
                                                        (errcode(ERRCODE_SYNTAX_ERROR),
                                                         errmsg("constraint declared INITIALLY DEFERRED must be DEFERRABLE"),
-                                                        parser_errposition(cxt->pstate, con->location)));
+                                                        parser_errposition(pstate, con->location)));
                                break;
 
                        case CONSTR_ATTR_DEFERRED:
@@ -4261,12 +4267,12 @@ transformConstraintAttrs(CreateStmtContext *cxt, List *constraintList)
                                        ereport(ERROR,
                                                        (errcode(ERRCODE_SYNTAX_ERROR),
                                                         errmsg("misplaced INITIALLY DEFERRED clause"),
-                                                        parser_errposition(cxt->pstate, con->location)));
+                                                        parser_errposition(pstate, con->location)));
                                if (saw_initially)
                                        ereport(ERROR,
                                                        (errcode(ERRCODE_SYNTAX_ERROR),
                                                         errmsg("multiple INITIALLY IMMEDIATE/DEFERRED clauses not allowed"),
-                                                        parser_errposition(cxt->pstate, con->location)));
+                                                        parser_errposition(pstate, con->location)));
                                saw_initially = true;
                                lastprimarycon->initdeferred = true;
 
@@ -4279,7 +4285,7 @@ transformConstraintAttrs(CreateStmtContext *cxt, List *constraintList)
                                        ereport(ERROR,
                                                        (errcode(ERRCODE_SYNTAX_ERROR),
                                                         errmsg("constraint declared INITIALLY DEFERRED must be DEFERRABLE"),
-                                                        parser_errposition(cxt->pstate, con->location)));
+                                                        parser_errposition(pstate, con->location)));
                                break;
 
                        case CONSTR_ATTR_IMMEDIATE:
@@ -4287,12 +4293,12 @@ transformConstraintAttrs(CreateStmtContext *cxt, List *constraintList)
                                        ereport(ERROR,
                                                        (errcode(ERRCODE_SYNTAX_ERROR),
                                                         errmsg("misplaced INITIALLY IMMEDIATE clause"),
-                                                        parser_errposition(cxt->pstate, con->location)));
+                                                        parser_errposition(pstate, con->location)));
                                if (saw_initially)
                                        ereport(ERROR,
                                                        (errcode(ERRCODE_SYNTAX_ERROR),
                                                         errmsg("multiple INITIALLY IMMEDIATE/DEFERRED clauses not allowed"),
-                                                        parser_errposition(cxt->pstate, con->location)));
+                                                        parser_errposition(pstate, con->location)));
                                saw_initially = true;
                                lastprimarycon->initdeferred = false;
                                break;
@@ -4304,12 +4310,12 @@ transformConstraintAttrs(CreateStmtContext *cxt, List *constraintList)
                                        ereport(ERROR,
                                                        (errcode(ERRCODE_SYNTAX_ERROR),
                                                         errmsg("misplaced ENFORCED clause"),
-                                                        parser_errposition(cxt->pstate, con->location)));
+                                                        parser_errposition(pstate, con->location)));
                                if (saw_enforced)
                                        ereport(ERROR,
                                                        (errcode(ERRCODE_SYNTAX_ERROR),
                                                         errmsg("multiple ENFORCED/NOT ENFORCED clauses not allowed"),
-                                                        parser_errposition(cxt->pstate, con->location)));
+                                                        parser_errposition(pstate, con->location)));
                                saw_enforced = true;
                                lastprimarycon->is_enforced = true;
                                break;
@@ -4321,12 +4327,12 @@ transformConstraintAttrs(CreateStmtContext *cxt, List *constraintList)
                                        ereport(ERROR,
                                                        (errcode(ERRCODE_SYNTAX_ERROR),
                                                         errmsg("misplaced NOT ENFORCED clause"),
-                                                        parser_errposition(cxt->pstate, con->location)));
+                                                        parser_errposition(pstate, con->location)));
                                if (saw_enforced)
                                        ereport(ERROR,
                                                        (errcode(ERRCODE_SYNTAX_ERROR),
                                                         errmsg("multiple ENFORCED/NOT ENFORCED clauses not allowed"),
-                                                        parser_errposition(cxt->pstate, con->location)));
+                                                        parser_errposition(pstate, con->location)));
                                saw_enforced = true;
                                lastprimarycon->is_enforced = false;
 
@@ -4384,12 +4390,17 @@ transformColumnType(CreateStmtContext *cxt, ColumnDef *column)
  * transformCreateSchemaStmtElements -
  *       analyzes the elements of a CREATE SCHEMA statement
  *
- * This is now somewhat vestigial: its only real responsibility is to complain
- * if any of the elements are trying to create objects outside the new schema.
+ * This presently has two responsibilities.  We verify that no subcommands are
+ * trying to create objects outside the new schema.  We also pull out any
+ * foreign-key constraint clauses embedded in CREATE TABLE subcommands, and
+ * convert them to ALTER TABLE ADD CONSTRAINT commands appended to the list.
+ * This supports forward references in foreign keys, which is required by the
+ * SQL standard.
+ *
  * We used to try to re-order the commands in a way that would work even if
  * the user-written order would not, but that's too hard (perhaps impossible)
  * to do correctly with not-yet-parse-analyzed commands.  Now we'll just
- * execute the elements in the order given.
+ * execute the elements in the order given, except for foreign keys.
  *
  * "schemaName" is the name of the schema that will be used for the creation
  * of the objects listed.  It may be obtained from the schema name defined
@@ -4398,12 +4409,17 @@ transformColumnType(CreateStmtContext *cxt, ColumnDef *column)
  * The result is a list of parse nodes that still need to be analyzed ---
  * but we can't analyze the later commands until we've executed the earlier
  * ones, because of possible inter-object references.
+ *
+ * Note it's important that we not modify the input data structure.  We create
+ * a new result List, and we copy any CREATE TABLE subcommands that we might
+ * modify.
  */
 List *
 transformCreateSchemaStmtElements(ParseState *pstate, List *schemaElts,
                                                                  const char *schemaName)
 {
        List       *elements = NIL;
+       List       *fk_elements = NIL;
        ListCell   *lc;
 
        /*
@@ -4430,7 +4446,11 @@ transformCreateSchemaStmtElements(ParseState *pstate, List *schemaElts,
                                        CreateStmt *elp = (CreateStmt *) element;
 
                                        checkSchemaNameRV(pstate, schemaName, elp->relation);
-                                       elements = lappend(elements, element);
+                                       /* Pull out any foreign key clauses, add to fk_elements */
+                                       elp = transformCreateSchemaCreateTable(pstate,
+                                                                                                                  elp,
+                                                                                                                  &fk_elements);
+                                       elements = lappend(elements, elp);
                                }
                                break;
 
@@ -4471,7 +4491,7 @@ transformCreateSchemaStmtElements(ParseState *pstate, List *schemaElts,
                }
        }
 
-       return elements;
+       return list_concat(elements, fk_elements);
 }
 
 /*
@@ -4508,6 +4528,161 @@ checkSchemaNameRV(ParseState *pstate, const char *context_schema,
        }
 }
 
+/*
+ * transformCreateSchemaCreateTable
+ *             Process one CreateStmt for transformCreateSchemaStmtElements.
+ *
+ * We remove any foreign-key clauses in the statement and convert them into
+ * ALTER TABLE commands, which we append to *fk_elements.
+ */
+static CreateStmt *
+transformCreateSchemaCreateTable(ParseState *pstate,
+                                                                CreateStmt *stmt,
+                                                                List **fk_elements)
+{
+       CreateStmt *newstmt;
+       List       *newElts = NIL;
+       ListCell   *lc;
+
+       /*
+        * Flat-copy the CreateStmt node, allowing us to replace its tableElts
+        * list without damaging the input data structure.  Most sub-nodes will be
+        * shared with the input, though.
+        */
+       newstmt = makeNode(CreateStmt);
+       memcpy(newstmt, stmt, sizeof(CreateStmt));
+
+       /* Scan for foreign-key constraints */
+       foreach(lc, stmt->tableElts)
+       {
+               Node       *element = lfirst(lc);
+               AlterTableStmt *alterstmt;
+               AlterTableCmd *altercmd;
+
+               if (IsA(element, Constraint))
+               {
+                       Constraint *constr = (Constraint *) element;
+
+                       if (constr->contype != CONSTR_FOREIGN)
+                       {
+                               /* Other constraint types pass through unchanged */
+                               newElts = lappend(newElts, constr);
+                               continue;
+                       }
+
+                       /* Make it into an ALTER TABLE ADD CONSTRAINT command */
+                       altercmd = makeNode(AlterTableCmd);
+                       altercmd->subtype = AT_AddConstraint;
+                       altercmd->name = NULL;
+                       altercmd->def = (Node *) copyObject(constr);
+
+                       alterstmt = makeNode(AlterTableStmt);
+                       alterstmt->relation = copyObject(stmt->relation);
+                       alterstmt->cmds = list_make1(altercmd);
+                       alterstmt->objtype = OBJECT_TABLE;
+
+                       *fk_elements = lappend(*fk_elements, alterstmt);
+               }
+               else if (IsA(element, ColumnDef))
+               {
+                       ColumnDef  *entry = (ColumnDef *) element;
+                       ColumnDef  *newentry;
+                       List       *entryconstraints;
+                       bool            afterFK = false;
+
+                       /*
+                        * We must preprocess the list of column constraints to attach
+                        * attributes such as DEFERRED to the appropriate constraint node.
+                        * Do this on a copy.  (But execution of the CreateStmt will run
+                        * transformConstraintAttrs on the copy, so we are nonetheless
+                        * relying on transformConstraintAttrs to be idempotent.)
+                        */
+                       entryconstraints = copyObject(entry->constraints);
+                       transformConstraintAttrs(pstate, entryconstraints);
+
+                       /* Scan the column constraints ... */
+                       foreach_node(Constraint, colconstr, entryconstraints)
+                       {
+                               switch (colconstr->contype)
+                               {
+                                       case CONSTR_FOREIGN:
+                                               /* colconstr is already a copy, OK to modify */
+                                               colconstr->fk_attrs = list_make1(makeString(entry->colname));
+
+                                               /* Make it into an ALTER TABLE ADD CONSTRAINT command */
+                                               altercmd = makeNode(AlterTableCmd);
+                                               altercmd->subtype = AT_AddConstraint;
+                                               altercmd->name = NULL;
+                                               altercmd->def = (Node *) colconstr;
+
+                                               alterstmt = makeNode(AlterTableStmt);
+                                               alterstmt->relation = copyObject(stmt->relation);
+                                               alterstmt->cmds = list_make1(altercmd);
+                                               alterstmt->objtype = OBJECT_TABLE;
+
+                                               *fk_elements = lappend(*fk_elements, alterstmt);
+
+                                               /* Remove the Constraint node from entryconstraints */
+                                               entryconstraints =
+                                                       foreach_delete_current(entryconstraints, colconstr);
+
+                                               /*
+                                                * Immediately-following attribute constraints should
+                                                * be dropped, too.
+                                                */
+                                               afterFK = true;
+                                               break;
+
+                                               /*
+                                                * Column constraint lists separate a Constraint node
+                                                * from its attributes (e.g. NOT ENFORCED); so a
+                                                * column-level foreign key constraint may be
+                                                * represented by multiple Constraint nodes.  After
+                                                * transformConstraintAttrs, the foreign key
+                                                * Constraint node contains all required information,
+                                                * making it okay to put into *fk_elements as a
+                                                * stand-alone Constraint.  But since we removed the
+                                                * foreign key Constraint node from entryconstraints,
+                                                * we must remove any dependent attribute nodes too,
+                                                * else the later re-execution of
+                                                * transformConstraintAttrs will misbehave.
+                                                */
+                                       case CONSTR_ATTR_DEFERRABLE:
+                                       case CONSTR_ATTR_NOT_DEFERRABLE:
+                                       case CONSTR_ATTR_DEFERRED:
+                                       case CONSTR_ATTR_IMMEDIATE:
+                                       case CONSTR_ATTR_ENFORCED:
+                                       case CONSTR_ATTR_NOT_ENFORCED:
+                                               if (afterFK)
+                                                       entryconstraints =
+                                                               foreach_delete_current(entryconstraints,
+                                                                                                          colconstr);
+                                               break;
+
+                                       default:
+                                               /* Any following constraint attributes are unrelated */
+                                               afterFK = false;
+                                               break;
+                               }
+                       }
+
+                       /* Now make a modified ColumnDef to put into newElts */
+                       newentry = makeNode(ColumnDef);
+                       memcpy(newentry, entry, sizeof(ColumnDef));
+                       newentry->constraints = entryconstraints;
+                       newElts = lappend(newElts, newentry);
+               }
+               else
+               {
+                       /* Other node types pass through unchanged */
+                       newElts = lappend(newElts, element);
+               }
+       }
+
+       newstmt->tableElts = newElts;
+       return newstmt;
+}
+
 /*
  * transformPartitionCmd
  *             Analyze the ATTACH/DETACH/SPLIT PARTITION command
index 8ab4eb03385230e7ed2fdf57c8a10089bb4d89d3..6ed85ef7446cbb8150323aad64ff82f5157445e1 100644 (file)
@@ -17,3 +17,28 @@ CREATE SCHEMA element_test
 NOTICE:  DDL test: type simple, tag CREATE SCHEMA
 NOTICE:  DDL test: type simple, tag CREATE TABLE
 NOTICE:  DDL test: type simple, tag CREATE VIEW
+CREATE SCHEMA regress_schema_1
+CREATE TABLE t4(
+    b INT,
+    a INT REFERENCES t5 DEFERRABLE INITIALLY DEFERRED NOT ENFORCED
+          REFERENCES t6 DEFERRABLE INITIALLY DEFERRED,
+    CONSTRAINT fk FOREIGN KEY (a) REFERENCES t6 DEFERRABLE)
+CREATE TABLE t5 (a INT, b INT, PRIMARY KEY (a))
+CREATE TABLE t6 (a INT, b INT, PRIMARY KEY (a));
+NOTICE:  DDL test: type simple, tag CREATE SCHEMA
+NOTICE:  DDL test: type simple, tag CREATE TABLE
+NOTICE:  DDL test: type simple, tag CREATE TABLE
+NOTICE:  DDL test: type simple, tag CREATE INDEX
+NOTICE:  DDL test: type simple, tag CREATE TABLE
+NOTICE:  DDL test: type simple, tag CREATE INDEX
+NOTICE:  DDL test: type alter table, tag ALTER TABLE
+NOTICE:    subcommand: type ADD CONSTRAINT (and recurse) desc constraint t4_a_fkey on table regress_schema_1.t4
+NOTICE:  DDL test: type alter table, tag ALTER TABLE
+NOTICE:    subcommand: type ADD CONSTRAINT (and recurse) desc constraint t4_a_fkey1 on table regress_schema_1.t4
+NOTICE:  DDL test: type alter table, tag ALTER TABLE
+NOTICE:    subcommand: type ADD CONSTRAINT (and recurse) desc constraint fk on table regress_schema_1.t4
+DROP SCHEMA regress_schema_1 CASCADE;
+NOTICE:  drop cascades to 3 other objects
+DETAIL:  drop cascades to table regress_schema_1.t4
+drop cascades to table regress_schema_1.t5
+drop cascades to table regress_schema_1.t6
index f314dc2b840b81a953f76a0a43d63fa66254bf4c..145aef2a75afccf8cf2695997e247a762dc0daea 100644 (file)
@@ -15,3 +15,14 @@ CREATE SCHEMA IF NOT EXISTS baz;
 CREATE SCHEMA element_test
   CREATE TABLE foo (id int)
   CREATE VIEW bar AS SELECT * FROM foo;
+
+CREATE SCHEMA regress_schema_1
+CREATE TABLE t4(
+    b INT,
+    a INT REFERENCES t5 DEFERRABLE INITIALLY DEFERRED NOT ENFORCED
+          REFERENCES t6 DEFERRABLE INITIALLY DEFERRED,
+    CONSTRAINT fk FOREIGN KEY (a) REFERENCES t6 DEFERRABLE)
+CREATE TABLE t5 (a INT, b INT, PRIMARY KEY (a))
+CREATE TABLE t6 (a INT, b INT, PRIMARY KEY (a));
+
+DROP SCHEMA regress_schema_1 CASCADE;
index 4ab947a60a8ac52d4e8174ef3542a0ede3cdf13f..b34b99889628a1e12a99df228642c44ea799cbd6 100644 (file)
@@ -131,5 +131,51 @@ CREATE SCHEMA regress_schema_1 AUTHORIZATION CURRENT_ROLE
 DROP SCHEMA regress_schema_1 CASCADE;
 NOTICE:  drop cascades to table regress_schema_1.tab
 RESET ROLE;
+-- Test forward-referencing foreign key clauses.
+CREATE SCHEMA regress_schema_fk
+    CREATE TABLE regress_schema_fk.t2 (
+        b int,
+        a int REFERENCES t1 DEFERRABLE INITIALLY DEFERRED NOT ENFORCED
+              REFERENCES t3 DEFERRABLE INITIALLY DEFERRED,
+        CONSTRAINT fk FOREIGN KEY (a) REFERENCES t1 NOT DEFERRABLE)
+    CREATE TABLE regress_schema_fk.t1 (a int PRIMARY KEY)
+    CREATE TABLE t3 (a int PRIMARY KEY)
+    CREATE TABLE t4 (
+        b int,
+        a int REFERENCES t5 NOT DEFERRABLE ENFORCED
+              REFERENCES t6 DEFERRABLE INITIALLY IMMEDIATE,
+        CONSTRAINT fk FOREIGN KEY (a) REFERENCES t6 DEFERRABLE INITIALLY DEFERRED)
+    CREATE TABLE t5 (a int, b int, PRIMARY KEY (a))
+    CREATE TABLE t6 (a int, b int, PRIMARY KEY (a));
+\d regress_schema_fk.t2
+           Table "regress_schema_fk.t2"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ b      | integer |           |          | 
+ a      | integer |           |          | 
+Foreign-key constraints:
+    "fk" FOREIGN KEY (a) REFERENCES regress_schema_fk.t1(a)
+    "t2_a_fkey" FOREIGN KEY (a) REFERENCES regress_schema_fk.t1(a) DEFERRABLE INITIALLY DEFERRED NOT ENFORCED
+    "t2_a_fkey1" FOREIGN KEY (a) REFERENCES regress_schema_fk.t3(a) DEFERRABLE INITIALLY DEFERRED
+
+\d regress_schema_fk.t4
+           Table "regress_schema_fk.t4"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ b      | integer |           |          | 
+ a      | integer |           |          | 
+Foreign-key constraints:
+    "fk" FOREIGN KEY (a) REFERENCES regress_schema_fk.t6(a) DEFERRABLE INITIALLY DEFERRED
+    "t4_a_fkey" FOREIGN KEY (a) REFERENCES regress_schema_fk.t5(a)
+    "t4_a_fkey1" FOREIGN KEY (a) REFERENCES regress_schema_fk.t6(a) DEFERRABLE
+
+DROP SCHEMA regress_schema_fk CASCADE;
+NOTICE:  drop cascades to 6 other objects
+DETAIL:  drop cascades to table regress_schema_fk.t2
+drop cascades to table regress_schema_fk.t1
+drop cascades to table regress_schema_fk.t3
+drop cascades to table regress_schema_fk.t4
+drop cascades to table regress_schema_fk.t5
+drop cascades to table regress_schema_fk.t6
 -- Clean up
 DROP ROLE regress_create_schema_role;
index 4c32e1dcaf5f6d32f59611785ef6ec4fe175588c..065f586310ff69a40ccdc7c151b99156f7d9c3dd 100644 (file)
@@ -427,11 +427,11 @@ NOTICE:  END: command_tag=ALTER SEQUENCE type=sequence identity=evttrig.one_col_
 NOTICE:  END: command_tag=ALTER SEQUENCE type=sequence identity=evttrig.one_col_c_seq
 NOTICE:  END: command_tag=CREATE INDEX type=index identity=evttrig.one_idx
 NOTICE:  END: command_tag=CREATE TABLE type=table identity=evttrig.two
-NOTICE:  END: command_tag=ALTER TABLE type=table identity=evttrig.two
 NOTICE:  END: command_tag=CREATE SEQUENCE type=sequence identity=evttrig.id_col_d_seq
 NOTICE:  END: command_tag=CREATE TABLE type=table identity=evttrig.id
 NOTICE:  END: command_tag=ALTER SEQUENCE type=sequence identity=evttrig.id_col_d_seq
 NOTICE:  END: command_tag=CREATE VIEW type=view identity=evttrig.one_view
+NOTICE:  END: command_tag=ALTER TABLE type=table identity=evttrig.two
 -- View with column additions
 CREATE OR REPLACE VIEW evttrig.one_view AS SELECT * FROM evttrig.two, evttrig.id;
 NOTICE:  END: command_tag=CREATE VIEW type=view identity=evttrig.one_view
index 62651342114db930d465932d3acacbbb83b64c74..0f2accc59ec411666eb2b75c245855e3efa2a7ec 100644 (file)
@@ -71,5 +71,32 @@ CREATE SCHEMA regress_schema_1 AUTHORIZATION CURRENT_ROLE
 DROP SCHEMA regress_schema_1 CASCADE;
 RESET ROLE;
 
+-- Test forward-referencing foreign key clauses.
+CREATE SCHEMA regress_schema_fk
+    CREATE TABLE regress_schema_fk.t2 (
+        b int,
+        a int REFERENCES t1 DEFERRABLE INITIALLY DEFERRED NOT ENFORCED
+              REFERENCES t3 DEFERRABLE INITIALLY DEFERRED,
+        CONSTRAINT fk FOREIGN KEY (a) REFERENCES t1 NOT DEFERRABLE)
+
+    CREATE TABLE regress_schema_fk.t1 (a int PRIMARY KEY)
+
+    CREATE TABLE t3 (a int PRIMARY KEY)
+
+    CREATE TABLE t4 (
+        b int,
+        a int REFERENCES t5 NOT DEFERRABLE ENFORCED
+              REFERENCES t6 DEFERRABLE INITIALLY IMMEDIATE,
+        CONSTRAINT fk FOREIGN KEY (a) REFERENCES t6 DEFERRABLE INITIALLY DEFERRED)
+
+    CREATE TABLE t5 (a int, b int, PRIMARY KEY (a))
+
+    CREATE TABLE t6 (a int, b int, PRIMARY KEY (a));
+
+\d regress_schema_fk.t2
+\d regress_schema_fk.t4
+
+DROP SCHEMA regress_schema_fk CASCADE;
+
 -- Clean up
 DROP ROLE regress_create_schema_role;