From d0abc5e8fb7392623a6826de7d6abc4b2ac65c0d Mon Sep 17 00:00:00 2001
From: Tom Lane <tgl@sss.pgh.pa.us>
Date: Sat, 4 Apr 2026 21:32:34 -0400
Subject: [PATCH v12 2/3] Execute foreign key constraints in CREATE SCHEMA at
 the end.

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.  (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 ambiguities.)

To fix that, 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>
Reviewed-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           |   4 +
 src/backend/parser/parse_utilcmd.c            | 219 ++++++++++++++++--
 .../expected/create_schema.out                |  25 ++
 .../test_ddl_deparse/sql/create_schema.sql    |  11 +
 src/test/regress/expected/create_schema.out   |  46 ++++
 src/test/regress/expected/event_trigger.out   |   2 +-
 src/test/regress/sql/create_schema.sql        |  27 +++
 7 files changed, 311 insertions(+), 23 deletions(-)

diff --git a/doc/src/sgml/ref/create_schema.sgml b/doc/src/sgml/ref/create_schema.sgml
index 32367c71b25..8e3c61890b8 100644
--- a/doc/src/sgml/ref/create_schema.sgml
+++ b/doc/src/sgml/ref/create_schema.sgml
@@ -201,6 +201,10 @@ CREATE VIEW hollywood.winners AS
    external reference rather than a forward one.
    Instead, <productname>PostgreSQL</productname> executes the subcommands
    in <command>CREATE SCHEMA</command> in the order they are given.
+   The sole exception is that foreign key constraint clauses
+   in <command>CREATE TABLE</command> subcommands can be forward
+   references to later <command>CREATE TABLE</command> subcommands.
+   This allows circular foreign key references, which are sometimes useful.
   </para>
 
   <para>
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index cb2e57610b2..1bc5df7e0ef 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -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 checkSchemaName(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;
 
 					checkSchemaName(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);
 }
 
 /*
@@ -4507,6 +4527,161 @@ checkSchemaName(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
diff --git a/src/test/modules/test_ddl_deparse/expected/create_schema.out b/src/test/modules/test_ddl_deparse/expected/create_schema.out
index 8ab4eb03385..6ed85ef7446 100644
--- a/src/test/modules/test_ddl_deparse/expected/create_schema.out
+++ b/src/test/modules/test_ddl_deparse/expected/create_schema.out
@@ -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
diff --git a/src/test/modules/test_ddl_deparse/sql/create_schema.sql b/src/test/modules/test_ddl_deparse/sql/create_schema.sql
index f314dc2b840..145aef2a75a 100644
--- a/src/test/modules/test_ddl_deparse/sql/create_schema.sql
+++ b/src/test/modules/test_ddl_deparse/sql/create_schema.sql
@@ -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;
diff --git a/src/test/regress/expected/create_schema.out b/src/test/regress/expected/create_schema.out
index 4ab947a60a8..b34b9988962 100644
--- a/src/test/regress/expected/create_schema.out
+++ b/src/test/regress/expected/create_schema.out
@@ -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;
diff --git a/src/test/regress/expected/event_trigger.out b/src/test/regress/expected/event_trigger.out
index 4c32e1dcaf5..065f586310f 100644
--- a/src/test/regress/expected/event_trigger.out
+++ b/src/test/regress/expected/event_trigger.out
@@ -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
diff --git a/src/test/regress/sql/create_schema.sql b/src/test/regress/sql/create_schema.sql
index 62651342114..0f2accc59ec 100644
--- a/src/test/regress/sql/create_schema.sql
+++ b/src/test/regress/sql/create_schema.sql
@@ -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;
-- 
2.43.7

