public inbox for [email protected]  
help / color / mirror / Atom feed
Re: CREATE SCHEMA ... CREATE DOMAIN support
32+ messages / 6 participants
[nested] [flat]

* Re: CREATE SCHEMA ... CREATE DOMAIN support
@ 2024-11-30 19:08 Tom Lane <[email protected]>
  2024-11-30 23:33 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  2024-12-03 19:35 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Peter Eisentraut <[email protected]>
  0 siblings, 2 replies; 32+ messages in thread

From: Tom Lane @ 2024-11-30 19:08 UTC (permalink / raw)
  To: jian he <[email protected]>; +Cc: Peter Eisentraut <[email protected]>; Kirill Reshke <[email protected]>; PostgreSQL Hackers <[email protected]>

[ Looping in Peter E. for commentary on SQL-spec compatibility ]

I spent some time looking at this patch, and came away with
two main thoughts:

1. It doesn't make any sense to me to support CREATE DOMAIN within
CREATE SCHEMA but not any of our other commands for creating types.
It's not a consistent feature this way, and there's no support for
it in the SQL standard either, because the spec calls out both
<domain definition> and <user-defined type definition> as permissible
schema elements.  So I think we need a bit more ambition in the scope
of the patch: it should allow every variant of CREATE TYPE too.
(Since the spec also lists <schema routine>, I'd personally like to
see us cover functions/procedures as well as types.  But functions
could be left for later I guess.)

2. transformCreateSchemaStmtElements is of the opinion that it's
responsible for ordering the schema elements in a way that will work,
but it just about completely fails at that task.  Ordering the objects
by kind is surely not sufficient, and adding CREATE DOMAIN will make
that worse.  (Example: a domain could be used in a table definition,
but we also allow domains to be created over tables' composite types.)
Yet we have no infrastructure that would allow us to discover the real
dependencies between unparsed DDL commands, nor is it likely that
anyone will ever undertake building such.  I think we ought to nuke
that concept from orbit and just execute the schema elements in the
order presented.  I looked at several iterations of the SQL standard
and cannot find any support for the idea that CREATE SCHEMA needs to
be any smarter than that.  I'd also argue that doing anything else is
a POLA violation.  It's especially a POLA violation if the code
rearranges a valid user-written command order into an invalid order,
which is inevitable if we stick with the current approach.

The notion that we ought to sort the objects by kind appears to go
all the way back to 95ef6a344 of 2002-03-21, which I guess makes it
my fault.  There must have been some prior mailing list discussion,
but I couldn't find much.  There is a predecessor of the committed
patch in
https://www.postgresql.org/message-id/flat/3C7F8A49.CC4EF0BE%40redhat.com
but no discussion of why sorting by kind is a good idea.  (The last
message in the thread suggests that there was more discussion among
the Red Hat RHDB team, but if so it's lost to history now.)

Thoughts?

			regards, tom lane






^ permalink  raw  reply  [nested|flat] 32+ messages in thread

* Re: CREATE SCHEMA ... CREATE DOMAIN support
  2024-11-30 19:08 Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
@ 2024-11-30 23:33 ` Tom Lane <[email protected]>
  2024-12-01 05:39   ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <[email protected]>
  1 sibling, 1 reply; 32+ messages in thread

From: Tom Lane @ 2024-11-30 23:33 UTC (permalink / raw)
  To: Peter Eisentraut <[email protected]>; +Cc: jian he <[email protected]>; Kirill Reshke <[email protected]>; PostgreSQL Hackers <[email protected]>

I wrote:
> 2. transformCreateSchemaStmtElements is of the opinion that it's
> responsible for ordering the schema elements in a way that will work,
> but it just about completely fails at that task.  Ordering the objects
> by kind is surely not sufficient, and adding CREATE DOMAIN will make
> that worse.  (Example: a domain could be used in a table definition,
> but we also allow domains to be created over tables' composite types.)
> Yet we have no infrastructure that would allow us to discover the real
> dependencies between unparsed DDL commands, nor is it likely that
> anyone will ever undertake building such.  I think we ought to nuke
> that concept from orbit and just execute the schema elements in the
> order presented.  I looked at several iterations of the SQL standard
> and cannot find any support for the idea that CREATE SCHEMA needs to
> be any smarter than that.  I'd also argue that doing anything else is
> a POLA violation.  It's especially a POLA violation if the code
> rearranges a valid user-written command order into an invalid order,
> which is inevitable if we stick with the current approach.

Further to this: I don't think "re-order into a safe order" is even
a well-defined requirement.  What should happen with

    CREATE VIEW public.v1 AS SELECT * FROM foo;

    CREATE SCHEMA s1

        CREATE VIEW v0 AS SELECT * FROM v1;

        CREATE VIEW v1 AS SELECT * FROM bar;

If we re-order the CREATE VIEW subcommands, this means something
different than if we don't.  Maybe the user meant us to re-order,
but it's hardly an open-and-shut argument.  And historically we
have not re-ordered CREATE VIEW subcommands, so there's a hazard
of compatibility problems if we did ever try to do that.

So attached is a draft patch that simplifies the rule to "do the
subcommands in the order written".  I took the opportunity to clean up
some other small infelicities about transformCreateSchemaStmtElements,
too.

			regards, tom lane



Attachments:

  [text/x-diff] v1-0001-Don-t-try-to-re-order-the-subcommands-of-CREATE-S.patch (24.8K, 2-v1-0001-Don-t-try-to-re-order-the-subcommands-of-CREATE-S.patch)
  download | inline diff:
From 14e47a5b64fb115928630613d9ab0ae2d6e05eec Mon Sep 17 00:00:00 2001
From: Tom Lane <[email protected]>
Date: Sat, 30 Nov 2024 18:11:04 -0500
Subject: [PATCH v1] Don't try to re-order the subcommands of CREATE SCHEMA.

transformCreateSchemaStmtElements has always believed that it is
supposed to re-order the subcommands of CREATE SCHEMA into a safe
execution order.  However, it is nowhere near being capable of doing
that correctly.  Nor is there reason to think that it ever will be,
or that that is a well-defined requirement, or that there's any basis
in the SQL standard for it.  Moreover, the problem will get worse as
we add more subcommand types.  Let's just drop the whole idea and
execute the commands in the order given, which seems like a much less
astonishment-prone definition anyway.

Along the way, pass down a ParseState so that we can provide an
error cursor for the "wrong schema name" error, and fix
transformCreateSchemaStmtElements so that it doesn't scribble
on the parsetree passed to it.

Discussion: https://postgr.es/m/[email protected]
---
 doc/src/sgml/ref/create_schema.sgml         |  10 +-
 src/backend/commands/extension.c            |   9 +-
 src/backend/commands/schemacmds.c           |  22 ++--
 src/backend/parser/parse_utilcmd.c          | 130 ++++++++------------
 src/backend/tcop/utility.c                  |   7 +-
 src/include/commands/schemacmds.h           |   7 +-
 src/include/parser/parse_utilcmd.h          |   3 +-
 src/test/regress/expected/create_schema.out |  30 +++++
 src/test/regress/expected/event_trigger.out |   2 +-
 src/test/regress/expected/namespace.out     |   9 +-
 src/test/regress/sql/namespace.sql          |  12 +-
 11 files changed, 123 insertions(+), 118 deletions(-)

diff --git a/doc/src/sgml/ref/create_schema.sgml b/doc/src/sgml/ref/create_schema.sgml
index ed69298ccc..625793a6b6 100644
--- a/doc/src/sgml/ref/create_schema.sgml
+++ b/doc/src/sgml/ref/create_schema.sgml
@@ -193,12 +193,10 @@ CREATE VIEW hollywood.winners AS
   </para>
 
   <para>
-   The SQL standard specifies that the subcommands in <command>CREATE
-   SCHEMA</command> can appear in any order.  The present
-   <productname>PostgreSQL</productname> implementation does not
-   handle all cases of forward references in subcommands; it might
-   sometimes be necessary to reorder the subcommands in order to avoid
-   forward references.
+   <productname>PostgreSQL</productname> executes the subcommands
+   in <command>CREATE SCHEMA</command> in the order given.  Other
+   implementations may try to rearrange the subcommands into dependency
+   order, but that is hard if not impossible to do correctly.
   </para>
 
   <para>
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index af6bd8ff42..a2eb42dc7f 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1687,14 +1687,19 @@ CreateExtensionInternal(char *extensionName,
 
 		if (!OidIsValid(schemaOid))
 		{
+			ParseState *pstate = make_parsestate(NULL);
 			CreateSchemaStmt *csstmt = makeNode(CreateSchemaStmt);
 
+			pstate->p_sourcetext = "(generated CREATE SCHEMA command)";
+			pstate->p_stmt_location = -1;
+			pstate->p_stmt_len = -1;
+
 			csstmt->schemaname = schemaName;
 			csstmt->authrole = NULL;	/* will be created by current user */
 			csstmt->schemaElts = NIL;
 			csstmt->if_not_exists = false;
-			CreateSchemaCommand(csstmt, "(generated CREATE SCHEMA command)",
-								-1, -1);
+
+			CreateSchemaCommand(pstate, csstmt);
 
 			/*
 			 * CreateSchemaCommand includes CommandCounterIncrement, so new
diff --git a/src/backend/commands/schemacmds.c b/src/backend/commands/schemacmds.c
index 233f8ad1d4..4470b9a402 100644
--- a/src/backend/commands/schemacmds.c
+++ b/src/backend/commands/schemacmds.c
@@ -42,15 +42,14 @@ static void AlterSchemaOwner_internal(HeapTuple tup, Relation rel, Oid newOwnerI
 /*
  * CREATE SCHEMA
  *
- * Note: caller should pass in location information for the whole
+ * Note: pstate should pass in location information for the whole
  * CREATE SCHEMA statement, which in turn we pass down as the location
  * of the component commands.  This comports with our general plan of
  * reporting location/len for the whole command even when executing
  * a subquery.
  */
 Oid
-CreateSchemaCommand(CreateSchemaStmt *stmt, const char *queryString,
-					int stmt_location, int stmt_len)
+CreateSchemaCommand(ParseState *pstate, CreateSchemaStmt *stmt)
 {
 	const char *schemaName = stmt->schemaname;
 	Oid			namespaceId;
@@ -189,12 +188,13 @@ CreateSchemaCommand(CreateSchemaStmt *stmt, const char *queryString,
 
 	/*
 	 * Examine the list of commands embedded in the CREATE SCHEMA command, and
-	 * reorganize them into a sequentially executable order with no forward
-	 * references.  Note that the result is still a list of raw parsetrees ---
-	 * we cannot, in general, run parse analysis on one statement until we
-	 * have actually executed the prior ones.
+	 * do preliminary transformations (mostly, verify that none are trying to
+	 * create objects outside the new schema).  Note that the result is still
+	 * a list of raw parsetrees --- we cannot, in general, run parse analysis
+	 * on one statement until we have actually executed the prior ones.
 	 */
-	parsetree_list = transformCreateSchemaStmtElements(stmt->schemaElts,
+	parsetree_list = transformCreateSchemaStmtElements(pstate,
+													   stmt->schemaElts,
 													   schemaName);
 
 	/*
@@ -213,12 +213,12 @@ CreateSchemaCommand(CreateSchemaStmt *stmt, const char *queryString,
 		wrapper->commandType = CMD_UTILITY;
 		wrapper->canSetTag = false;
 		wrapper->utilityStmt = stmt;
-		wrapper->stmt_location = stmt_location;
-		wrapper->stmt_len = stmt_len;
+		wrapper->stmt_location = pstate->p_stmt_location;
+		wrapper->stmt_len = pstate->p_stmt_len;
 
 		/* do this step */
 		ProcessUtility(wrapper,
-					   queryString,
+					   pstate->p_sourcetext,
 					   false,
 					   PROCESS_UTILITY_SUBCOMMAND,
 					   NULL,
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 0f324ee4e3..70993637ad 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -95,18 +95,6 @@ typedef struct
 	bool		ofType;			/* true if statement contains OF typename */
 } CreateStmtContext;
 
-/* State shared by transformCreateSchemaStmtElements and its subroutines */
-typedef struct
-{
-	const char *schemaname;		/* name of schema */
-	List	   *sequences;		/* CREATE SEQUENCE items */
-	List	   *tables;			/* CREATE TABLE items */
-	List	   *views;			/* CREATE VIEW items */
-	List	   *indexes;		/* CREATE INDEX items */
-	List	   *triggers;		/* CREATE TRIGGER items */
-	List	   *grants;			/* GRANT items */
-} CreateSchemaStmtContext;
-
 
 static void transformColumnDefinition(CreateStmtContext *cxt,
 									  ColumnDef *column);
@@ -133,7 +121,8 @@ static void transformCheckConstraints(CreateStmtContext *cxt,
 static void transformConstraintAttrs(CreateStmtContext *cxt,
 									 List *constraintList);
 static void transformColumnType(CreateStmtContext *cxt, ColumnDef *column);
-static void setSchemaName(const char *context_schema, char **stmt_schema_name);
+static void checkSchemaName(ParseState *pstate, const char *context_schema,
+							RangeVar *relation);
 static void transformPartitionCmd(CreateStmtContext *cxt, PartitionCmd *cmd);
 static List *transformPartitionRangeBounds(ParseState *pstate, List *blist,
 										   Relation parent);
@@ -4002,51 +3991,35 @@ transformColumnType(CreateStmtContext *cxt, ColumnDef *column)
  * transformCreateSchemaStmtElements -
  *	  analyzes the elements of a CREATE SCHEMA statement
  *
- * Split the schema element list from a CREATE SCHEMA statement into
- * individual commands and place them in the result list in an order
- * such that there are no forward references (e.g. GRANT to a table
- * created later in the list). Note that the logic we use for determining
- * forward references is presently quite incomplete.
+ * 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.
+ * 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.
  *
  * "schemaName" is the name of the schema that will be used for the creation
- * of the objects listed, that may be compiled from the schema name defined
+ * of the objects listed.  It may be obtained from the schema name defined
  * in the statement or a role specification.
  *
- * SQL also allows constraints to make forward references, so thumb through
- * the table columns and move forward references to a posterior alter-table
- * command.
- *
  * 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: this breaks the rules a little bit by modifying schema-name fields
- * within passed-in structs.  However, the transformation would be the same
- * if done over, so it should be all right to scribble on the input to this
- * extent.
  */
 List *
-transformCreateSchemaStmtElements(List *schemaElts, const char *schemaName)
+transformCreateSchemaStmtElements(ParseState *pstate, List *schemaElts,
+								  const char *schemaName)
 {
-	CreateSchemaStmtContext cxt;
-	List	   *result;
-	ListCell   *elements;
-
-	cxt.schemaname = schemaName;
-	cxt.sequences = NIL;
-	cxt.tables = NIL;
-	cxt.views = NIL;
-	cxt.indexes = NIL;
-	cxt.triggers = NIL;
-	cxt.grants = NIL;
+	List	   *elements = NIL;
+	ListCell   *lc;
 
 	/*
-	 * Run through each schema element in the schema element list. Separate
-	 * statements by type, and do preliminary analysis.
+	 * Run through each schema element in the schema element list.  Check
+	 * target schema names, and collect the list of actions to be done.
 	 */
-	foreach(elements, schemaElts)
+	foreach(lc, schemaElts)
 	{
-		Node	   *element = lfirst(elements);
+		Node	   *element = lfirst(lc);
 
 		switch (nodeTag(element))
 		{
@@ -4054,8 +4027,8 @@ transformCreateSchemaStmtElements(List *schemaElts, const char *schemaName)
 				{
 					CreateSeqStmt *elp = (CreateSeqStmt *) element;
 
-					setSchemaName(cxt.schemaname, &elp->sequence->schemaname);
-					cxt.sequences = lappend(cxt.sequences, element);
+					checkSchemaName(pstate, schemaName, elp->sequence);
+					elements = lappend(elements, element);
 				}
 				break;
 
@@ -4063,12 +4036,8 @@ transformCreateSchemaStmtElements(List *schemaElts, const char *schemaName)
 				{
 					CreateStmt *elp = (CreateStmt *) element;
 
-					setSchemaName(cxt.schemaname, &elp->relation->schemaname);
-
-					/*
-					 * XXX todo: deal with constraints
-					 */
-					cxt.tables = lappend(cxt.tables, element);
+					checkSchemaName(pstate, schemaName, elp->relation);
+					elements = lappend(elements, element);
 				}
 				break;
 
@@ -4076,12 +4045,8 @@ transformCreateSchemaStmtElements(List *schemaElts, const char *schemaName)
 				{
 					ViewStmt   *elp = (ViewStmt *) element;
 
-					setSchemaName(cxt.schemaname, &elp->view->schemaname);
-
-					/*
-					 * XXX todo: deal with references between views
-					 */
-					cxt.views = lappend(cxt.views, element);
+					checkSchemaName(pstate, schemaName, elp->view);
+					elements = lappend(elements, element);
 				}
 				break;
 
@@ -4089,8 +4054,8 @@ transformCreateSchemaStmtElements(List *schemaElts, const char *schemaName)
 				{
 					IndexStmt  *elp = (IndexStmt *) element;
 
-					setSchemaName(cxt.schemaname, &elp->relation->schemaname);
-					cxt.indexes = lappend(cxt.indexes, element);
+					checkSchemaName(pstate, schemaName, elp->relation);
+					elements = lappend(elements, element);
 				}
 				break;
 
@@ -4098,13 +4063,13 @@ transformCreateSchemaStmtElements(List *schemaElts, const char *schemaName)
 				{
 					CreateTrigStmt *elp = (CreateTrigStmt *) element;
 
-					setSchemaName(cxt.schemaname, &elp->relation->schemaname);
-					cxt.triggers = lappend(cxt.triggers, element);
+					checkSchemaName(pstate, schemaName, elp->relation);
+					elements = lappend(elements, element);
 				}
 				break;
 
 			case T_GrantStmt:
-				cxt.grants = lappend(cxt.grants, element);
+				elements = lappend(elements, element);
 				break;
 
 			default:
@@ -4113,32 +4078,39 @@ transformCreateSchemaStmtElements(List *schemaElts, const char *schemaName)
 		}
 	}
 
-	result = NIL;
-	result = list_concat(result, cxt.sequences);
-	result = list_concat(result, cxt.tables);
-	result = list_concat(result, cxt.views);
-	result = list_concat(result, cxt.indexes);
-	result = list_concat(result, cxt.triggers);
-	result = list_concat(result, cxt.grants);
-
-	return result;
+	return elements;
 }
 
 /*
- * setSchemaName
- *		Set or check schema name in an element of a CREATE SCHEMA command
+ * checkSchemaName
+ *		Check schema name in an element of a CREATE SCHEMA command
+ *
+ * It's okay if the command doesn't specify a target schema name, because
+ * CreateSchemaCommand will set up the default creation schema to be the
+ * new schema.  But if a target schema name is given, it had better match.
+ * We also have to check that the command doesn't say CREATE TEMP, since
+ * that would likewise put the object into the wrong schema.
  */
 static void
-setSchemaName(const char *context_schema, char **stmt_schema_name)
+checkSchemaName(ParseState *pstate, const char *context_schema,
+				RangeVar *relation)
 {
-	if (*stmt_schema_name == NULL)
-		*stmt_schema_name = unconstify(char *, context_schema);
-	else if (strcmp(context_schema, *stmt_schema_name) != 0)
+	if (relation->schemaname != NULL &&
+		strcmp(context_schema, relation->schemaname) != 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_SCHEMA_DEFINITION),
 				 errmsg("CREATE specifies a schema (%s) "
 						"different from the one being created (%s)",
-						*stmt_schema_name, context_schema)));
+						relation->schemaname, context_schema),
+				 parser_errposition(pstate, relation->location)));
+
+	if (relation->relpersistence == RELPERSISTENCE_TEMP)
+	{
+		/* spell this error the same as in RangeVarAdjustRelationPersistence */
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+				 errmsg("cannot create temporary relation in non-temporary schema")));
+	}
 }
 
 /*
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index f28bf37105..ad0a314630 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -587,6 +587,8 @@ standard_ProcessUtility(PlannedStmt *pstmt,
 
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
+	pstate->p_stmt_location = pstmt->stmt_location;
+	pstate->p_stmt_len = pstmt->stmt_len;
 	pstate->p_queryEnv = queryEnv;
 
 	switch (nodeTag(parsetree))
@@ -1121,10 +1123,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				 * relation and attribute manipulation
 				 */
 			case T_CreateSchemaStmt:
-				CreateSchemaCommand((CreateSchemaStmt *) parsetree,
-									queryString,
-									pstmt->stmt_location,
-									pstmt->stmt_len);
+				CreateSchemaCommand(pstate, (CreateSchemaStmt *) parsetree);
 
 				/*
 				 * EventTriggerCollectSimpleCommand called by
diff --git a/src/include/commands/schemacmds.h b/src/include/commands/schemacmds.h
index 5598dfa5d7..e6861994d0 100644
--- a/src/include/commands/schemacmds.h
+++ b/src/include/commands/schemacmds.h
@@ -16,12 +16,9 @@
 #define SCHEMACMDS_H
 
 #include "catalog/objectaddress.h"
-#include "nodes/parsenodes.h"
-
-extern Oid	CreateSchemaCommand(CreateSchemaStmt *stmt,
-								const char *queryString,
-								int stmt_location, int stmt_len);
+#include "parser/parse_node.h"
 
+extern Oid	CreateSchemaCommand(ParseState *pstate, CreateSchemaStmt *stmt);
 extern ObjectAddress RenameSchema(const char *oldname, const char *newname);
 extern ObjectAddress AlterSchemaOwner(const char *name, Oid newOwnerId);
 extern void AlterSchemaOwner_oid(Oid schemaoid, Oid newOwnerId);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 1406589477..5d22ea988c 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -30,7 +30,8 @@ extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-extern List *transformCreateSchemaStmtElements(List *schemaElts,
+extern List *transformCreateSchemaStmtElements(ParseState *pstate,
+											   List *schemaElts,
 											   const char *schemaName);
 extern PartitionBoundSpec *transformPartitionBound(ParseState *pstate, Relation parent,
 												   PartitionBoundSpec *spec);
diff --git a/src/test/regress/expected/create_schema.out b/src/test/regress/expected/create_schema.out
index 93302a07ef..554f0f3c50 100644
--- a/src/test/regress/expected/create_schema.out
+++ b/src/test/regress/expected/create_schema.out
@@ -9,54 +9,84 @@ CREATE ROLE regress_create_schema_role SUPERUSER;
 CREATE SCHEMA AUTHORIZATION regress_create_schema_role
   CREATE SEQUENCE schema_not_existing.seq;
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_create_schema_role)
+LINE 2:   CREATE SEQUENCE schema_not_existing.seq;
+                          ^
 CREATE SCHEMA AUTHORIZATION regress_create_schema_role
   CREATE TABLE schema_not_existing.tab (id int);
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_create_schema_role)
+LINE 2:   CREATE TABLE schema_not_existing.tab (id int);
+                       ^
 CREATE SCHEMA AUTHORIZATION regress_create_schema_role
   CREATE VIEW schema_not_existing.view AS SELECT 1;
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_create_schema_role)
+LINE 2:   CREATE VIEW schema_not_existing.view AS SELECT 1;
+                      ^
 CREATE SCHEMA AUTHORIZATION regress_create_schema_role
   CREATE INDEX ON schema_not_existing.tab (id);
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_create_schema_role)
+LINE 2:   CREATE INDEX ON schema_not_existing.tab (id);
+                          ^
 CREATE SCHEMA AUTHORIZATION regress_create_schema_role
   CREATE TRIGGER schema_trig BEFORE INSERT ON schema_not_existing.tab
   EXECUTE FUNCTION schema_trig.no_func();
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_create_schema_role)
+LINE 2:   CREATE TRIGGER schema_trig BEFORE INSERT ON schema_not_exi...
+                                                      ^
 -- Again, with a role specification and no schema names.
 SET ROLE regress_create_schema_role;
 CREATE SCHEMA AUTHORIZATION CURRENT_ROLE
   CREATE SEQUENCE schema_not_existing.seq;
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_create_schema_role)
+LINE 2:   CREATE SEQUENCE schema_not_existing.seq;
+                          ^
 CREATE SCHEMA AUTHORIZATION CURRENT_ROLE
   CREATE TABLE schema_not_existing.tab (id int);
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_create_schema_role)
+LINE 2:   CREATE TABLE schema_not_existing.tab (id int);
+                       ^
 CREATE SCHEMA AUTHORIZATION CURRENT_ROLE
   CREATE VIEW schema_not_existing.view AS SELECT 1;
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_create_schema_role)
+LINE 2:   CREATE VIEW schema_not_existing.view AS SELECT 1;
+                      ^
 CREATE SCHEMA AUTHORIZATION CURRENT_ROLE
   CREATE INDEX ON schema_not_existing.tab (id);
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_create_schema_role)
+LINE 2:   CREATE INDEX ON schema_not_existing.tab (id);
+                          ^
 CREATE SCHEMA AUTHORIZATION CURRENT_ROLE
   CREATE TRIGGER schema_trig BEFORE INSERT ON schema_not_existing.tab
   EXECUTE FUNCTION schema_trig.no_func();
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_create_schema_role)
+LINE 2:   CREATE TRIGGER schema_trig BEFORE INSERT ON schema_not_exi...
+                                                      ^
 -- Again, with a schema name and a role specification.
 CREATE SCHEMA regress_schema_1 AUTHORIZATION CURRENT_ROLE
   CREATE SEQUENCE schema_not_existing.seq;
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_schema_1)
+LINE 2:   CREATE SEQUENCE schema_not_existing.seq;
+                          ^
 CREATE SCHEMA regress_schema_1 AUTHORIZATION CURRENT_ROLE
   CREATE TABLE schema_not_existing.tab (id int);
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_schema_1)
+LINE 2:   CREATE TABLE schema_not_existing.tab (id int);
+                       ^
 CREATE SCHEMA regress_schema_1 AUTHORIZATION CURRENT_ROLE
   CREATE VIEW schema_not_existing.view AS SELECT 1;
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_schema_1)
+LINE 2:   CREATE VIEW schema_not_existing.view AS SELECT 1;
+                      ^
 CREATE SCHEMA regress_schema_1 AUTHORIZATION CURRENT_ROLE
   CREATE INDEX ON schema_not_existing.tab (id);
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_schema_1)
+LINE 2:   CREATE INDEX ON schema_not_existing.tab (id);
+                          ^
 CREATE SCHEMA regress_schema_1 AUTHORIZATION CURRENT_ROLE
   CREATE TRIGGER schema_trig BEFORE INSERT ON schema_not_existing.tab
   EXECUTE FUNCTION schema_trig.no_func();
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_schema_1)
+LINE 2:   CREATE TRIGGER schema_trig BEFORE INSERT ON schema_not_exi...
+                                                      ^
 RESET ROLE;
 -- Cases where the schema creation succeeds.
 -- The schema created matches the role name.
diff --git a/src/test/regress/expected/event_trigger.out b/src/test/regress/expected/event_trigger.out
index 7b2198eac6..bafde8706f 100644
--- a/src/test/regress/expected/event_trigger.out
+++ b/src/test/regress/expected/event_trigger.out
@@ -411,12 +411,12 @@ NOTICE:  END: command_tag=CREATE TABLE type=table identity=evttrig.one
 NOTICE:  END: command_tag=CREATE INDEX type=index identity=evttrig.one_pkey
 NOTICE:  END: command_tag=ALTER SEQUENCE type=sequence identity=evttrig.one_col_a_seq
 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 INDEX type=index identity=evttrig.one_idx
 -- Partitioned tables with a partitioned index
 CREATE TABLE evttrig.parted (
     id int PRIMARY KEY)
diff --git a/src/test/regress/expected/namespace.out b/src/test/regress/expected/namespace.out
index dbbda72d39..2e582e783c 100644
--- a/src/test/regress/expected/namespace.out
+++ b/src/test/regress/expected/namespace.out
@@ -10,13 +10,14 @@ SELECT pg_catalog.set_config('search_path', ' ', false);
 (1 row)
 
 CREATE SCHEMA test_ns_schema_1
-       CREATE UNIQUE INDEX abc_a_idx ON abc (a)
-       CREATE VIEW abc_view AS
-              SELECT a+1 AS a, b+1 AS b FROM abc
        CREATE TABLE abc (
               a serial,
               b int UNIQUE
-       );
+       )
+       CREATE UNIQUE INDEX abc_a_idx ON abc (a)
+       CREATE VIEW abc_view AS
+              SELECT a+1 AS a, b+1 AS b FROM abc
+;
 -- verify that the correct search_path restored on abort
 SET search_path to public;
 BEGIN;
diff --git a/src/test/regress/sql/namespace.sql b/src/test/regress/sql/namespace.sql
index 306cdc2d8c..62a75e63d2 100644
--- a/src/test/regress/sql/namespace.sql
+++ b/src/test/regress/sql/namespace.sql
@@ -7,15 +7,17 @@
 SELECT pg_catalog.set_config('search_path', ' ', false);
 
 CREATE SCHEMA test_ns_schema_1
-       CREATE UNIQUE INDEX abc_a_idx ON abc (a)
-
-       CREATE VIEW abc_view AS
-              SELECT a+1 AS a, b+1 AS b FROM abc
 
        CREATE TABLE abc (
               a serial,
               b int UNIQUE
-       );
+       )
+
+       CREATE UNIQUE INDEX abc_a_idx ON abc (a)
+
+       CREATE VIEW abc_view AS
+              SELECT a+1 AS a, b+1 AS b FROM abc
+;
 
 -- verify that the correct search_path restored on abort
 SET search_path to public;
-- 
2.43.5



^ permalink  raw  reply  [nested|flat] 32+ messages in thread

* Re: CREATE SCHEMA ... CREATE DOMAIN support
  2024-11-30 19:08 Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  2024-11-30 23:33 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
@ 2024-12-01 05:39   ` Kirill Reshke <[email protected]>
  2024-12-01 05:53     ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  0 siblings, 1 reply; 32+ messages in thread

From: Kirill Reshke @ 2024-12-01 05:39 UTC (permalink / raw)
  To: Tom Lane <[email protected]>; +Cc: Peter Eisentraut <[email protected]>; jian he <[email protected]>; PostgreSQL Hackers <[email protected]>

On Sun, 1 Dec 2024 at 04:33, Tom Lane <[email protected]> wrote:
> I looked at several iterations of the SQL standard
> and cannot find any support for the idea that CREATE SCHEMA needs to
> be any smarter than that.  I'd also argue that doing anything else is
> a POLA violation.  It's especially a POLA violation if the code
> rearranges a valid user-written command order into an invalid order,
> which is inevitable if we stick with the current approach.

Agreed.

> So attached is a draft patch that simplifies the rule to "do the
> subcommands in the order written".

+1 on this idea.

Here is my 2c to this draft:
1) Report error position on newly added check for temp relation in
non-temp schema.

> +                 errmsg("cannot create temporary relation in non-temporary schema"),
> +                 parser_errposition(pstate, relation->location)));

2)
I added some regression tests that might be worth adding:
a) check for a temporary table created within a non-temporary schema
in create_table.sql, akin to existing check in create_view.sql.
b) Also explicitly check that old-style sql creation does not work.
That is, for example,
CREATE SCHEMA test_ns_schema_3
       CREATE VIEW abcd_view AS
              SELECT a FROM abcd
       CREATE TABLE abcd (
              a serial
       );

fails.

3) Why do we delete this in `create_schema.sgml`? Is this untrue? It
is about order of definition, not creation, isn't it?

> -   The SQL standard specifies that the subcommands in <command>CREATE
> -   SCHEMA</command> can appear in any order.


P.S.
This section in SQL-92 is the only information I could find about
order of creation.

```
         3) Those objects defined by <schema element>s (base tables, views,
            constraints, domains, assertions, character sets, translations,
            collations, privileges) and their associated descriptors are
            effectively created.
```
Look like we are 100% to do it in order of definition


-- 
Best regards,
Kirill Reshke


Attachments:

  [application/octet-stream] v2-0001-Don-t-try-to-re-order-the-subcommands-of-CREATE-S.patch (28.5K, 2-v2-0001-Don-t-try-to-re-order-the-subcommands-of-CREATE-S.patch)
  download | inline diff:
From 565132d93cf189077d19dba48240002306f62cd1 Mon Sep 17 00:00:00 2001
From: Tom Lane <[email protected]>
Date: Sat, 30 Nov 2024 18:11:04 -0500
Subject: [PATCH v2] Don't try to re-order the subcommands of CREATE SCHEMA.

transformCreateSchemaStmtElements has always believed that it is
supposed to re-order the subcommands of CREATE SCHEMA into a safe
execution order.  However, it is nowhere near being capable of doing
that correctly.  Nor is there reason to think that it ever will be,
or that that is a well-defined requirement, or that there's any basis
in the SQL standard for it.  Moreover, the problem will get worse as
we add more subcommand types.  Let's just drop the whole idea and
execute the commands in the order given, which seems like a much less
astonishment-prone definition anyway.

Along the way, pass down a ParseState so that we can provide an
error cursor for the "wrong schema name" error, and fix
transformCreateSchemaStmtElements so that it doesn't scribble
on the parsetree passed to it.

Discussion: https://postgr.es/m/[email protected]
---
 doc/src/sgml/ref/create_schema.sgml         |  10 +-
 src/backend/commands/extension.c            |   9 +-
 src/backend/commands/schemacmds.c           |  22 ++--
 src/backend/parser/parse_utilcmd.c          | 131 ++++++++------------
 src/backend/tcop/utility.c                  |   7 +-
 src/include/commands/schemacmds.h           |   7 +-
 src/include/parser/parse_utilcmd.h          |   3 +-
 src/test/regress/expected/create_schema.out |  30 +++++
 src/test/regress/expected/create_table.out  |   9 ++
 src/test/regress/expected/create_view.out   |   2 +
 src/test/regress/expected/event_trigger.out |   2 +-
 src/test/regress/expected/namespace.out     |  19 ++-
 src/test/regress/sql/create_table.sql       |   6 +
 src/test/regress/sql/namespace.sql          |  22 +++-
 14 files changed, 161 insertions(+), 118 deletions(-)

diff --git a/doc/src/sgml/ref/create_schema.sgml b/doc/src/sgml/ref/create_schema.sgml
index ed69298ccc6..625793a6b67 100644
--- a/doc/src/sgml/ref/create_schema.sgml
+++ b/doc/src/sgml/ref/create_schema.sgml
@@ -193,12 +193,10 @@ CREATE VIEW hollywood.winners AS
   </para>
 
   <para>
-   The SQL standard specifies that the subcommands in <command>CREATE
-   SCHEMA</command> can appear in any order.  The present
-   <productname>PostgreSQL</productname> implementation does not
-   handle all cases of forward references in subcommands; it might
-   sometimes be necessary to reorder the subcommands in order to avoid
-   forward references.
+   <productname>PostgreSQL</productname> executes the subcommands
+   in <command>CREATE SCHEMA</command> in the order given.  Other
+   implementations may try to rearrange the subcommands into dependency
+   order, but that is hard if not impossible to do correctly.
   </para>
 
   <para>
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index af6bd8ff426..a2eb42dc7fb 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1687,14 +1687,19 @@ CreateExtensionInternal(char *extensionName,
 
 		if (!OidIsValid(schemaOid))
 		{
+			ParseState *pstate = make_parsestate(NULL);
 			CreateSchemaStmt *csstmt = makeNode(CreateSchemaStmt);
 
+			pstate->p_sourcetext = "(generated CREATE SCHEMA command)";
+			pstate->p_stmt_location = -1;
+			pstate->p_stmt_len = -1;
+
 			csstmt->schemaname = schemaName;
 			csstmt->authrole = NULL;	/* will be created by current user */
 			csstmt->schemaElts = NIL;
 			csstmt->if_not_exists = false;
-			CreateSchemaCommand(csstmt, "(generated CREATE SCHEMA command)",
-								-1, -1);
+
+			CreateSchemaCommand(pstate, csstmt);
 
 			/*
 			 * CreateSchemaCommand includes CommandCounterIncrement, so new
diff --git a/src/backend/commands/schemacmds.c b/src/backend/commands/schemacmds.c
index 233f8ad1d44..4470b9a4023 100644
--- a/src/backend/commands/schemacmds.c
+++ b/src/backend/commands/schemacmds.c
@@ -42,15 +42,14 @@ static void AlterSchemaOwner_internal(HeapTuple tup, Relation rel, Oid newOwnerI
 /*
  * CREATE SCHEMA
  *
- * Note: caller should pass in location information for the whole
+ * Note: pstate should pass in location information for the whole
  * CREATE SCHEMA statement, which in turn we pass down as the location
  * of the component commands.  This comports with our general plan of
  * reporting location/len for the whole command even when executing
  * a subquery.
  */
 Oid
-CreateSchemaCommand(CreateSchemaStmt *stmt, const char *queryString,
-					int stmt_location, int stmt_len)
+CreateSchemaCommand(ParseState *pstate, CreateSchemaStmt *stmt)
 {
 	const char *schemaName = stmt->schemaname;
 	Oid			namespaceId;
@@ -189,12 +188,13 @@ CreateSchemaCommand(CreateSchemaStmt *stmt, const char *queryString,
 
 	/*
 	 * Examine the list of commands embedded in the CREATE SCHEMA command, and
-	 * reorganize them into a sequentially executable order with no forward
-	 * references.  Note that the result is still a list of raw parsetrees ---
-	 * we cannot, in general, run parse analysis on one statement until we
-	 * have actually executed the prior ones.
+	 * do preliminary transformations (mostly, verify that none are trying to
+	 * create objects outside the new schema).  Note that the result is still
+	 * a list of raw parsetrees --- we cannot, in general, run parse analysis
+	 * on one statement until we have actually executed the prior ones.
 	 */
-	parsetree_list = transformCreateSchemaStmtElements(stmt->schemaElts,
+	parsetree_list = transformCreateSchemaStmtElements(pstate,
+													   stmt->schemaElts,
 													   schemaName);
 
 	/*
@@ -213,12 +213,12 @@ CreateSchemaCommand(CreateSchemaStmt *stmt, const char *queryString,
 		wrapper->commandType = CMD_UTILITY;
 		wrapper->canSetTag = false;
 		wrapper->utilityStmt = stmt;
-		wrapper->stmt_location = stmt_location;
-		wrapper->stmt_len = stmt_len;
+		wrapper->stmt_location = pstate->p_stmt_location;
+		wrapper->stmt_len = pstate->p_stmt_len;
 
 		/* do this step */
 		ProcessUtility(wrapper,
-					   queryString,
+					   pstate->p_sourcetext,
 					   false,
 					   PROCESS_UTILITY_SUBCOMMAND,
 					   NULL,
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 0f324ee4e31..6d00643f34b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -95,18 +95,6 @@ typedef struct
 	bool		ofType;			/* true if statement contains OF typename */
 } CreateStmtContext;
 
-/* State shared by transformCreateSchemaStmtElements and its subroutines */
-typedef struct
-{
-	const char *schemaname;		/* name of schema */
-	List	   *sequences;		/* CREATE SEQUENCE items */
-	List	   *tables;			/* CREATE TABLE items */
-	List	   *views;			/* CREATE VIEW items */
-	List	   *indexes;		/* CREATE INDEX items */
-	List	   *triggers;		/* CREATE TRIGGER items */
-	List	   *grants;			/* GRANT items */
-} CreateSchemaStmtContext;
-
 
 static void transformColumnDefinition(CreateStmtContext *cxt,
 									  ColumnDef *column);
@@ -133,7 +121,8 @@ static void transformCheckConstraints(CreateStmtContext *cxt,
 static void transformConstraintAttrs(CreateStmtContext *cxt,
 									 List *constraintList);
 static void transformColumnType(CreateStmtContext *cxt, ColumnDef *column);
-static void setSchemaName(const char *context_schema, char **stmt_schema_name);
+static void checkSchemaName(ParseState *pstate, const char *context_schema,
+							RangeVar *relation);
 static void transformPartitionCmd(CreateStmtContext *cxt, PartitionCmd *cmd);
 static List *transformPartitionRangeBounds(ParseState *pstate, List *blist,
 										   Relation parent);
@@ -4002,51 +3991,35 @@ transformColumnType(CreateStmtContext *cxt, ColumnDef *column)
  * transformCreateSchemaStmtElements -
  *	  analyzes the elements of a CREATE SCHEMA statement
  *
- * Split the schema element list from a CREATE SCHEMA statement into
- * individual commands and place them in the result list in an order
- * such that there are no forward references (e.g. GRANT to a table
- * created later in the list). Note that the logic we use for determining
- * forward references is presently quite incomplete.
+ * 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.
+ * 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.
  *
  * "schemaName" is the name of the schema that will be used for the creation
- * of the objects listed, that may be compiled from the schema name defined
+ * of the objects listed.  It may be obtained from the schema name defined
  * in the statement or a role specification.
  *
- * SQL also allows constraints to make forward references, so thumb through
- * the table columns and move forward references to a posterior alter-table
- * command.
- *
  * 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: this breaks the rules a little bit by modifying schema-name fields
- * within passed-in structs.  However, the transformation would be the same
- * if done over, so it should be all right to scribble on the input to this
- * extent.
  */
 List *
-transformCreateSchemaStmtElements(List *schemaElts, const char *schemaName)
+transformCreateSchemaStmtElements(ParseState *pstate, List *schemaElts,
+								  const char *schemaName)
 {
-	CreateSchemaStmtContext cxt;
-	List	   *result;
-	ListCell   *elements;
-
-	cxt.schemaname = schemaName;
-	cxt.sequences = NIL;
-	cxt.tables = NIL;
-	cxt.views = NIL;
-	cxt.indexes = NIL;
-	cxt.triggers = NIL;
-	cxt.grants = NIL;
+	List	   *elements = NIL;
+	ListCell   *lc;
 
 	/*
-	 * Run through each schema element in the schema element list. Separate
-	 * statements by type, and do preliminary analysis.
+	 * Run through each schema element in the schema element list.  Check
+	 * target schema names, and collect the list of actions to be done.
 	 */
-	foreach(elements, schemaElts)
+	foreach(lc, schemaElts)
 	{
-		Node	   *element = lfirst(elements);
+		Node	   *element = lfirst(lc);
 
 		switch (nodeTag(element))
 		{
@@ -4054,8 +4027,8 @@ transformCreateSchemaStmtElements(List *schemaElts, const char *schemaName)
 				{
 					CreateSeqStmt *elp = (CreateSeqStmt *) element;
 
-					setSchemaName(cxt.schemaname, &elp->sequence->schemaname);
-					cxt.sequences = lappend(cxt.sequences, element);
+					checkSchemaName(pstate, schemaName, elp->sequence);
+					elements = lappend(elements, element);
 				}
 				break;
 
@@ -4063,12 +4036,8 @@ transformCreateSchemaStmtElements(List *schemaElts, const char *schemaName)
 				{
 					CreateStmt *elp = (CreateStmt *) element;
 
-					setSchemaName(cxt.schemaname, &elp->relation->schemaname);
-
-					/*
-					 * XXX todo: deal with constraints
-					 */
-					cxt.tables = lappend(cxt.tables, element);
+					checkSchemaName(pstate, schemaName, elp->relation);
+					elements = lappend(elements, element);
 				}
 				break;
 
@@ -4076,12 +4045,8 @@ transformCreateSchemaStmtElements(List *schemaElts, const char *schemaName)
 				{
 					ViewStmt   *elp = (ViewStmt *) element;
 
-					setSchemaName(cxt.schemaname, &elp->view->schemaname);
-
-					/*
-					 * XXX todo: deal with references between views
-					 */
-					cxt.views = lappend(cxt.views, element);
+					checkSchemaName(pstate, schemaName, elp->view);
+					elements = lappend(elements, element);
 				}
 				break;
 
@@ -4089,8 +4054,8 @@ transformCreateSchemaStmtElements(List *schemaElts, const char *schemaName)
 				{
 					IndexStmt  *elp = (IndexStmt *) element;
 
-					setSchemaName(cxt.schemaname, &elp->relation->schemaname);
-					cxt.indexes = lappend(cxt.indexes, element);
+					checkSchemaName(pstate, schemaName, elp->relation);
+					elements = lappend(elements, element);
 				}
 				break;
 
@@ -4098,13 +4063,13 @@ transformCreateSchemaStmtElements(List *schemaElts, const char *schemaName)
 				{
 					CreateTrigStmt *elp = (CreateTrigStmt *) element;
 
-					setSchemaName(cxt.schemaname, &elp->relation->schemaname);
-					cxt.triggers = lappend(cxt.triggers, element);
+					checkSchemaName(pstate, schemaName, elp->relation);
+					elements = lappend(elements, element);
 				}
 				break;
 
 			case T_GrantStmt:
-				cxt.grants = lappend(cxt.grants, element);
+				elements = lappend(elements, element);
 				break;
 
 			default:
@@ -4113,32 +4078,40 @@ transformCreateSchemaStmtElements(List *schemaElts, const char *schemaName)
 		}
 	}
 
-	result = NIL;
-	result = list_concat(result, cxt.sequences);
-	result = list_concat(result, cxt.tables);
-	result = list_concat(result, cxt.views);
-	result = list_concat(result, cxt.indexes);
-	result = list_concat(result, cxt.triggers);
-	result = list_concat(result, cxt.grants);
-
-	return result;
+	return elements;
 }
 
 /*
- * setSchemaName
- *		Set or check schema name in an element of a CREATE SCHEMA command
+ * checkSchemaName
+ *		Check schema name in an element of a CREATE SCHEMA command
+ *
+ * It's okay if the command doesn't specify a target schema name, because
+ * CreateSchemaCommand will set up the default creation schema to be the
+ * new schema.  But if a target schema name is given, it had better match.
+ * We also have to check that the command doesn't say CREATE TEMP, since
+ * that would likewise put the object into the wrong schema.
  */
 static void
-setSchemaName(const char *context_schema, char **stmt_schema_name)
+checkSchemaName(ParseState *pstate, const char *context_schema,
+				RangeVar *relation)
 {
-	if (*stmt_schema_name == NULL)
-		*stmt_schema_name = unconstify(char *, context_schema);
-	else if (strcmp(context_schema, *stmt_schema_name) != 0)
+	if (relation->schemaname != NULL &&
+		strcmp(context_schema, relation->schemaname) != 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_SCHEMA_DEFINITION),
 				 errmsg("CREATE specifies a schema (%s) "
 						"different from the one being created (%s)",
-						*stmt_schema_name, context_schema)));
+						relation->schemaname, context_schema),
+				 parser_errposition(pstate, relation->location)));
+
+	if (relation->relpersistence == RELPERSISTENCE_TEMP)
+	{
+		/* spell this error the same as in RangeVarAdjustRelationPersistence */
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+				 errmsg("cannot create temporary relation in non-temporary schema"),
+				 parser_errposition(pstate, relation->location)));
+	}
 }
 
 /*
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index f28bf371059..ad0a3146309 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -587,6 +587,8 @@ standard_ProcessUtility(PlannedStmt *pstmt,
 
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
+	pstate->p_stmt_location = pstmt->stmt_location;
+	pstate->p_stmt_len = pstmt->stmt_len;
 	pstate->p_queryEnv = queryEnv;
 
 	switch (nodeTag(parsetree))
@@ -1121,10 +1123,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				 * relation and attribute manipulation
 				 */
 			case T_CreateSchemaStmt:
-				CreateSchemaCommand((CreateSchemaStmt *) parsetree,
-									queryString,
-									pstmt->stmt_location,
-									pstmt->stmt_len);
+				CreateSchemaCommand(pstate, (CreateSchemaStmt *) parsetree);
 
 				/*
 				 * EventTriggerCollectSimpleCommand called by
diff --git a/src/include/commands/schemacmds.h b/src/include/commands/schemacmds.h
index 5598dfa5d76..e6861994d03 100644
--- a/src/include/commands/schemacmds.h
+++ b/src/include/commands/schemacmds.h
@@ -16,12 +16,9 @@
 #define SCHEMACMDS_H
 
 #include "catalog/objectaddress.h"
-#include "nodes/parsenodes.h"
-
-extern Oid	CreateSchemaCommand(CreateSchemaStmt *stmt,
-								const char *queryString,
-								int stmt_location, int stmt_len);
+#include "parser/parse_node.h"
 
+extern Oid	CreateSchemaCommand(ParseState *pstate, CreateSchemaStmt *stmt);
 extern ObjectAddress RenameSchema(const char *oldname, const char *newname);
 extern ObjectAddress AlterSchemaOwner(const char *name, Oid newOwnerId);
 extern void AlterSchemaOwner_oid(Oid schemaoid, Oid newOwnerId);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 14065894779..5d22ea988c5 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -30,7 +30,8 @@ extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-extern List *transformCreateSchemaStmtElements(List *schemaElts,
+extern List *transformCreateSchemaStmtElements(ParseState *pstate,
+											   List *schemaElts,
 											   const char *schemaName);
 extern PartitionBoundSpec *transformPartitionBound(ParseState *pstate, Relation parent,
 												   PartitionBoundSpec *spec);
diff --git a/src/test/regress/expected/create_schema.out b/src/test/regress/expected/create_schema.out
index 93302a07efc..554f0f3c50b 100644
--- a/src/test/regress/expected/create_schema.out
+++ b/src/test/regress/expected/create_schema.out
@@ -9,54 +9,84 @@ CREATE ROLE regress_create_schema_role SUPERUSER;
 CREATE SCHEMA AUTHORIZATION regress_create_schema_role
   CREATE SEQUENCE schema_not_existing.seq;
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_create_schema_role)
+LINE 2:   CREATE SEQUENCE schema_not_existing.seq;
+                          ^
 CREATE SCHEMA AUTHORIZATION regress_create_schema_role
   CREATE TABLE schema_not_existing.tab (id int);
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_create_schema_role)
+LINE 2:   CREATE TABLE schema_not_existing.tab (id int);
+                       ^
 CREATE SCHEMA AUTHORIZATION regress_create_schema_role
   CREATE VIEW schema_not_existing.view AS SELECT 1;
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_create_schema_role)
+LINE 2:   CREATE VIEW schema_not_existing.view AS SELECT 1;
+                      ^
 CREATE SCHEMA AUTHORIZATION regress_create_schema_role
   CREATE INDEX ON schema_not_existing.tab (id);
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_create_schema_role)
+LINE 2:   CREATE INDEX ON schema_not_existing.tab (id);
+                          ^
 CREATE SCHEMA AUTHORIZATION regress_create_schema_role
   CREATE TRIGGER schema_trig BEFORE INSERT ON schema_not_existing.tab
   EXECUTE FUNCTION schema_trig.no_func();
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_create_schema_role)
+LINE 2:   CREATE TRIGGER schema_trig BEFORE INSERT ON schema_not_exi...
+                                                      ^
 -- Again, with a role specification and no schema names.
 SET ROLE regress_create_schema_role;
 CREATE SCHEMA AUTHORIZATION CURRENT_ROLE
   CREATE SEQUENCE schema_not_existing.seq;
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_create_schema_role)
+LINE 2:   CREATE SEQUENCE schema_not_existing.seq;
+                          ^
 CREATE SCHEMA AUTHORIZATION CURRENT_ROLE
   CREATE TABLE schema_not_existing.tab (id int);
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_create_schema_role)
+LINE 2:   CREATE TABLE schema_not_existing.tab (id int);
+                       ^
 CREATE SCHEMA AUTHORIZATION CURRENT_ROLE
   CREATE VIEW schema_not_existing.view AS SELECT 1;
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_create_schema_role)
+LINE 2:   CREATE VIEW schema_not_existing.view AS SELECT 1;
+                      ^
 CREATE SCHEMA AUTHORIZATION CURRENT_ROLE
   CREATE INDEX ON schema_not_existing.tab (id);
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_create_schema_role)
+LINE 2:   CREATE INDEX ON schema_not_existing.tab (id);
+                          ^
 CREATE SCHEMA AUTHORIZATION CURRENT_ROLE
   CREATE TRIGGER schema_trig BEFORE INSERT ON schema_not_existing.tab
   EXECUTE FUNCTION schema_trig.no_func();
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_create_schema_role)
+LINE 2:   CREATE TRIGGER schema_trig BEFORE INSERT ON schema_not_exi...
+                                                      ^
 -- Again, with a schema name and a role specification.
 CREATE SCHEMA regress_schema_1 AUTHORIZATION CURRENT_ROLE
   CREATE SEQUENCE schema_not_existing.seq;
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_schema_1)
+LINE 2:   CREATE SEQUENCE schema_not_existing.seq;
+                          ^
 CREATE SCHEMA regress_schema_1 AUTHORIZATION CURRENT_ROLE
   CREATE TABLE schema_not_existing.tab (id int);
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_schema_1)
+LINE 2:   CREATE TABLE schema_not_existing.tab (id int);
+                       ^
 CREATE SCHEMA regress_schema_1 AUTHORIZATION CURRENT_ROLE
   CREATE VIEW schema_not_existing.view AS SELECT 1;
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_schema_1)
+LINE 2:   CREATE VIEW schema_not_existing.view AS SELECT 1;
+                      ^
 CREATE SCHEMA regress_schema_1 AUTHORIZATION CURRENT_ROLE
   CREATE INDEX ON schema_not_existing.tab (id);
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_schema_1)
+LINE 2:   CREATE INDEX ON schema_not_existing.tab (id);
+                          ^
 CREATE SCHEMA regress_schema_1 AUTHORIZATION CURRENT_ROLE
   CREATE TRIGGER schema_trig BEFORE INSERT ON schema_not_existing.tab
   EXECUTE FUNCTION schema_trig.no_func();
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_schema_1)
+LINE 2:   CREATE TRIGGER schema_trig BEFORE INSERT ON schema_not_exi...
+                                                      ^
 RESET ROLE;
 -- Cases where the schema creation succeeds.
 -- The schema created matches the role name.
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 76604705a93..e90401c4c69 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -49,6 +49,15 @@ CREATE TEMP TABLE public.temp_to_perm (a int primary key);		-- not OK
 ERROR:  cannot create temporary relation in non-temporary schema
 LINE 1: CREATE TEMP TABLE public.temp_to_perm (a int primary key);
                           ^
+-- test error and error position with temporary table created within
+-- a non-temporary schema.
+CREATE SCHEMA test_ns_schema_tmp
+       CREATE TEMP TABLE abc (
+              a serial
+       );
+ERROR:  cannot create temporary relation in non-temporary schema
+LINE 2:        CREATE TEMP TABLE abc (
+                                 ^
 DROP TABLE unlogged1, public.unlogged2;
 CREATE UNLOGGED TABLE unlogged1 (a int) PARTITION BY RANGE (a); -- fail
 ERROR:  partitioned tables cannot be unlogged
diff --git a/src/test/regress/expected/create_view.out b/src/test/regress/expected/create_view.out
index f551624afb3..b305ceea033 100644
--- a/src/test/regress/expected/create_view.out
+++ b/src/test/regress/expected/create_view.out
@@ -126,6 +126,8 @@ ERROR:  cannot create temporary relation in non-temporary schema
 CREATE SCHEMA test_view_schema
     CREATE TEMP VIEW testview AS SELECT 1;
 ERROR:  cannot create temporary relation in non-temporary schema
+LINE 2:     CREATE TEMP VIEW testview AS SELECT 1;
+                             ^
 -- joins: if any of the join relations are temporary, the view
 -- should also be temporary
 -- should be non-temp
diff --git a/src/test/regress/expected/event_trigger.out b/src/test/regress/expected/event_trigger.out
index 7b2198eac6f..bafde8706fe 100644
--- a/src/test/regress/expected/event_trigger.out
+++ b/src/test/regress/expected/event_trigger.out
@@ -411,12 +411,12 @@ NOTICE:  END: command_tag=CREATE TABLE type=table identity=evttrig.one
 NOTICE:  END: command_tag=CREATE INDEX type=index identity=evttrig.one_pkey
 NOTICE:  END: command_tag=ALTER SEQUENCE type=sequence identity=evttrig.one_col_a_seq
 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 INDEX type=index identity=evttrig.one_idx
 -- Partitioned tables with a partitioned index
 CREATE TABLE evttrig.parted (
     id int PRIMARY KEY)
diff --git a/src/test/regress/expected/namespace.out b/src/test/regress/expected/namespace.out
index dbbda72d395..452d5689b22 100644
--- a/src/test/regress/expected/namespace.out
+++ b/src/test/regress/expected/namespace.out
@@ -10,13 +10,14 @@ SELECT pg_catalog.set_config('search_path', ' ', false);
 (1 row)
 
 CREATE SCHEMA test_ns_schema_1
-       CREATE UNIQUE INDEX abc_a_idx ON abc (a)
-       CREATE VIEW abc_view AS
-              SELECT a+1 AS a, b+1 AS b FROM abc
        CREATE TABLE abc (
               a serial,
               b int UNIQUE
-       );
+       )
+       CREATE UNIQUE INDEX abc_a_idx ON abc (a)
+       CREATE VIEW abc_view AS
+              SELECT a+1 AS a, b+1 AS b FROM abc
+;
 -- verify that the correct search_path restored on abort
 SET search_path to public;
 BEGIN;
@@ -54,6 +55,16 @@ SHOW search_path;
 
 DROP SCHEMA test_ns_schema_2 CASCADE;
 NOTICE:  drop cascades to view test_ns_schema_2.abc_view
+-- test error with wrong object creation order.
+CREATE SCHEMA test_ns_schema_3 
+       CREATE VIEW abcd_view AS
+              SELECT a FROM abcd
+       CREATE TABLE abcd (
+              a serial
+       );
+ERROR:  relation "abcd" does not exist
+LINE 3:               SELECT a FROM abcd
+                                    ^
 -- verify that the objects were created
 SELECT COUNT(*) FROM pg_class WHERE relnamespace =
     (SELECT oid FROM pg_namespace WHERE nspname = 'test_ns_schema_1');
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 37a227148e9..1e9237fc099 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -28,6 +28,12 @@ CREATE TABLE pg_temp.implicitly_temp (a int primary key);		-- OK
 CREATE TEMP TABLE explicitly_temp (a int primary key);			-- also OK
 CREATE TEMP TABLE pg_temp.doubly_temp (a int primary key);		-- also OK
 CREATE TEMP TABLE public.temp_to_perm (a int primary key);		-- not OK
+-- test error and error position with temporary table created within
+-- a non-temporary schema.
+CREATE SCHEMA test_ns_schema_tmp
+       CREATE TEMP TABLE abc (
+              a serial
+       );
 DROP TABLE unlogged1, public.unlogged2;
 
 CREATE UNLOGGED TABLE unlogged1 (a int) PARTITION BY RANGE (a); -- fail
diff --git a/src/test/regress/sql/namespace.sql b/src/test/regress/sql/namespace.sql
index 306cdc2d8c6..66aa1ad6eba 100644
--- a/src/test/regress/sql/namespace.sql
+++ b/src/test/regress/sql/namespace.sql
@@ -7,15 +7,17 @@
 SELECT pg_catalog.set_config('search_path', ' ', false);
 
 CREATE SCHEMA test_ns_schema_1
-       CREATE UNIQUE INDEX abc_a_idx ON abc (a)
-
-       CREATE VIEW abc_view AS
-              SELECT a+1 AS a, b+1 AS b FROM abc
 
        CREATE TABLE abc (
               a serial,
               b int UNIQUE
-       );
+       )
+
+       CREATE UNIQUE INDEX abc_a_idx ON abc (a)
+
+       CREATE VIEW abc_view AS
+              SELECT a+1 AS a, b+1 AS b FROM abc
+;
 
 -- verify that the correct search_path restored on abort
 SET search_path to public;
@@ -37,6 +39,16 @@ COMMIT;
 SHOW search_path;
 DROP SCHEMA test_ns_schema_2 CASCADE;
 
+-- test error with wrong object creation order.
+CREATE SCHEMA test_ns_schema_3 
+
+       CREATE VIEW abcd_view AS
+              SELECT a FROM abcd
+
+       CREATE TABLE abcd (
+              a serial
+       );
+
 -- verify that the objects were created
 SELECT COUNT(*) FROM pg_class WHERE relnamespace =
     (SELECT oid FROM pg_namespace WHERE nspname = 'test_ns_schema_1');
-- 
2.34.1



^ permalink  raw  reply  [nested|flat] 32+ messages in thread

* Re: CREATE SCHEMA ... CREATE DOMAIN support
  2024-11-30 19:08 Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  2024-11-30 23:33 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  2024-12-01 05:39   ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <[email protected]>
@ 2024-12-01 05:53     ` Tom Lane <[email protected]>
  2024-12-01 09:59       ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <[email protected]>
  0 siblings, 1 reply; 32+ messages in thread

From: Tom Lane @ 2024-12-01 05:53 UTC (permalink / raw)
  To: Kirill Reshke <[email protected]>; +Cc: Peter Eisentraut <[email protected]>; jian he <[email protected]>; PostgreSQL Hackers <[email protected]>

Kirill Reshke <[email protected]> writes:
> 3) Why do we delete this in `create_schema.sgml`? Is this untrue? It
> is about order of definition, not creation, isn't it?

>> -   The SQL standard specifies that the subcommands in <command>CREATE
>> -   SCHEMA</command> can appear in any order.

In context with the following sentence, what that is really trying
to say is that the spec requires us to re-order the subcommands
to eliminate forward references.  After studying the text I cannot
find any such statement.  Maybe I missed something --- there's a
lot of text --- but it's sure not to be detected in any obvious
place like 11.1 <schema definition>.

(I'd be curious to know how other major implementations handle
this.  Are we the only implementation that ever read the spec
that way?)

			regards, tom lane






^ permalink  raw  reply  [nested|flat] 32+ messages in thread

* Re: CREATE SCHEMA ... CREATE DOMAIN support
  2024-11-30 19:08 Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  2024-11-30 23:33 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  2024-12-01 05:39   ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <[email protected]>
  2024-12-01 05:53     ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
@ 2024-12-01 09:59       ` jian he <[email protected]>
  2024-12-01 16:57         ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  0 siblings, 1 reply; 32+ messages in thread

From: jian he @ 2024-12-01 09:59 UTC (permalink / raw)
  To: Tom Lane <[email protected]>; +Cc: Kirill Reshke <[email protected]>; Peter Eisentraut <[email protected]>; PostgreSQL Hackers <[email protected]>

On Sun, Dec 1, 2024 at 1:53 PM Tom Lane <[email protected]> wrote:
>
> Kirill Reshke <[email protected]> writes:
> > 3) Why do we delete this in `create_schema.sgml`? Is this untrue? It
> > is about order of definition, not creation, isn't it?
>
> >> -   The SQL standard specifies that the subcommands in <command>CREATE
> >> -   SCHEMA</command> can appear in any order.
>
> In context with the following sentence, what that is really trying
> to say is that the spec requires us to re-order the subcommands
> to eliminate forward references.  After studying the text I cannot
> find any such statement.  Maybe I missed something --- there's a
> lot of text --- but it's sure not to be detected in any obvious
> place like 11.1 <schema definition>.
>

I checked,  you didn't miss anything
11.1  didn't mention "order" at all.

> (I'd be curious to know how other major implementations handle
> this.  Are we the only implementation that ever read the spec
> that way?)
>

quote from  https://learn.microsoft.com/en-us/sql/t-sql/statements/create-schema-transact-sql?view=sql-server-ve...
<<>>
CREATE SCHEMA can create a schema, the tables and views it contains, and GRANT,
REVOKE, or DENY permissions on any securable in a single statement. This
statement must be executed as a separate batch. Objects created by the CREATE
SCHEMA statement are created inside the schema that is being created.

Securables to be created by CREATE SCHEMA can be listed in any order, except for
views that reference other views. In that case, the referenced view must be
created before the view that references it.

Therefore, a GRANT statement can grant permission on an object before the object
itself is created, or a CREATE VIEW statement can appear before the CREATE TABLE
statements that create the tables referenced by the view. Also, CREATE TABLE
statements can declare foreign keys to tables that are defined later in the
CREATE SCHEMA statement.
<<>>






^ permalink  raw  reply  [nested|flat] 32+ messages in thread

* Re: CREATE SCHEMA ... CREATE DOMAIN support
  2024-11-30 19:08 Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  2024-11-30 23:33 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  2024-12-01 05:39   ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <[email protected]>
  2024-12-01 05:53     ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  2024-12-01 09:59       ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <[email protected]>
@ 2024-12-01 16:57         ` Tom Lane <[email protected]>
  2024-12-01 22:30           ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  0 siblings, 1 reply; 32+ messages in thread

From: Tom Lane @ 2024-12-01 16:57 UTC (permalink / raw)
  To: jian he <[email protected]>; +Cc: Kirill Reshke <[email protected]>; Peter Eisentraut <[email protected]>; PostgreSQL Hackers <[email protected]>

jian he <[email protected]> writes:
> On Sun, Dec 1, 2024 at 1:53 PM Tom Lane <[email protected]> wrote:
>> (I'd be curious to know how other major implementations handle
>> this.  Are we the only implementation that ever read the spec
>> that way?)

> quote from  https://learn.microsoft.com/en-us/sql/t-sql/statements/create-schema-transact-sql?view=sql-server-ve...
> <<>>
> CREATE SCHEMA can create a schema, the tables and views it contains, and GRANT,
> REVOKE, or DENY permissions on any securable in a single statement. This
> statement must be executed as a separate batch. Objects created by the CREATE
> SCHEMA statement are created inside the schema that is being created.

> Securables to be created by CREATE SCHEMA can be listed in any order, except for
> views that reference other views. In that case, the referenced view must be
> created before the view that references it.

> Therefore, a GRANT statement can grant permission on an object before the object
> itself is created, or a CREATE VIEW statement can appear before the CREATE TABLE
> statements that create the tables referenced by the view. Also, CREATE TABLE
> statements can declare foreign keys to tables that are defined later in the
> CREATE SCHEMA statement.
> <<>>

Interesting.  But I suspect this tells us more about SQL Server's
internal implementation of DDL actions than about spec requirements.

I looked at DB2's reference page:
https://www.ibm.com/docs/en/db2/11.5?topic=statements-create-schema
It doesn't have much of anything explicit on this topic, but they do
give an example showing that you can create two tables with mutually
referencing foreign keys, which means they postpone FK constraint
creation till the end.  There's also this interesting tidbit:
"Unqualified object names in any SQL statement within the CREATE SCHEMA
statement are implicitly qualified by the name of the created schema."
which eliminates some of the is-that-an-external-reference-or-a-
forward-reference ambiguities I was concerned about yesterday.
That ship sailed decades ago for us, however.

I'm also interested to note that like SQL Server, DB2 has strict
limits on the types of objects that can be created, much narrower
than what the spec suggests.  For DB2 it's:

CREATE TABLE statement, excluding typed tables and materialized query tables
CREATE VIEW statement, excluding typed views
CREATE INDEX statement
COMMENT statement
GRANT statement

That suggests, even though they don't say so, that they're trying to
do forward-reference removal; there'd be little reason for the
restriction otherwise.

MySQL doesn't have CREATE SCHEMA (it's a synonym for CREATE DATABASE),
so nothing to be learned there.

Whether or not the standard has an opinion on this topic, it's pretty
clear that real implementations are all over the place and have plenty
of ad-hoc restrictions.  I'm still thinking that "let's forget all
that and do the subcommands in order" is a win for sanity and
explainability.

			regards, tom lane






^ permalink  raw  reply  [nested|flat] 32+ messages in thread

* Re: CREATE SCHEMA ... CREATE DOMAIN support
  2024-11-30 19:08 Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  2024-11-30 23:33 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  2024-12-01 05:39   ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <[email protected]>
  2024-12-01 05:53     ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  2024-12-01 09:59       ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <[email protected]>
  2024-12-01 16:57         ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
@ 2024-12-01 22:30           ` Tom Lane <[email protected]>
  2024-12-02 01:34             ` Re: CREATE SCHEMA ... CREATE DOMAIN support Michael Paquier <[email protected]>
  0 siblings, 1 reply; 32+ messages in thread

From: Tom Lane @ 2024-12-01 22:30 UTC (permalink / raw)
  To: jian he <[email protected]>; +Cc: Kirill Reshke <[email protected]>; Peter Eisentraut <[email protected]>; PostgreSQL Hackers <[email protected]>

I wrote:
> I looked at DB2's reference page:
> https://www.ibm.com/docs/en/db2/11.5?topic=statements-create-schema

Oh, how did I forget Oracle?

https://docs.oracle.com/en/database/oracle/oracle-database/23/sqlrf/CREATE-SCHEMA.html

Theirs is restricted to CREATE TABLE, CREATE VIEW, and GRANT; also
this curious restriction: "The CREATE SCHEMA statement supports the
syntax of these statements only as defined by standard SQL, rather
than the complete syntax supported by Oracle Database."

But then they say:

"The order in which you list the CREATE TABLE, CREATE VIEW, and GRANT
statements is unimportant. The statements within a CREATE SCHEMA
statement can reference existing objects or objects you create in
other statements within the same CREATE SCHEMA statement."

Which certainly begs the question of how smart their re-ordering
algorithm is, or what they do about ambiguity between new and existing
objects.  But at any rate, it looks like everybody is at least trying
to do some amount of re-ordering, which makes me wonder what it is
that I'm missing in the spec.  That's an awful lot of effort to be
expending on something that the spec doesn't seem to require.

			regards, tom lane






^ permalink  raw  reply  [nested|flat] 32+ messages in thread

* Re: CREATE SCHEMA ... CREATE DOMAIN support
  2024-11-30 19:08 Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  2024-11-30 23:33 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  2024-12-01 05:39   ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <[email protected]>
  2024-12-01 05:53     ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  2024-12-01 09:59       ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <[email protected]>
  2024-12-01 16:57         ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  2024-12-01 22:30           ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
@ 2024-12-02 01:34             ` Michael Paquier <[email protected]>
  2024-12-02 02:15               ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  0 siblings, 1 reply; 32+ messages in thread

From: Michael Paquier @ 2024-12-02 01:34 UTC (permalink / raw)
  To: Tom Lane <[email protected]>; +Cc: jian he <[email protected]>; Kirill Reshke <[email protected]>; Peter Eisentraut <[email protected]>; PostgreSQL Hackers <[email protected]>

On Sun, Dec 01, 2024 at 05:30:20PM -0500, Tom Lane wrote:
> Which certainly begs the question of how smart their re-ordering
> algorithm is, or what they do about ambiguity between new and existing
> objects.

Perhaps because they are able to track efficiently all schema
references, like checking the internal of functions at creation time
rather than just at runtime?  The ambiguity between new and existing
objects may be tricky, indeed.

If I'm parsing the spec right, the doc mentions in its 5)~6) of the
syntax rules in CREATE SCHEMA that non-schema-qualified objects should
use the new schema name defined in the CREATE SCHEMA query.  So that
pretty much settles the rules to use when having a new object that has
a reference to a non-qualified object created in the same CREATE
SCHEMA query?

> But at any rate, it looks like everybody is at least trying
> to do some amount of re-ordering, which makes me wonder what it is
> that I'm missing in the spec.  That's an awful lot of effort to be
> expending on something that the spec doesn't seem to require.

As Jian has mentioned, 9075-2-2023 around 11.1 for CREATE SCHEMA does
not include any ordering assumptions when the elements are created, so
my guess is that this is left up to each implementation depending on
how they need to handle their dependencies with their meta-data
lookup?  The result would be the same once the query has finished
running, as long as the elements created are consistent with their
inner dependencies.
--
Michael


Attachments:

  [application/pgp-signature] signature.asc (833B, 2-signature.asc)
  download

^ permalink  raw  reply  [nested|flat] 32+ messages in thread

* Re: CREATE SCHEMA ... CREATE DOMAIN support
  2024-11-30 19:08 Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  2024-11-30 23:33 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  2024-12-01 05:39   ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <[email protected]>
  2024-12-01 05:53     ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  2024-12-01 09:59       ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <[email protected]>
  2024-12-01 16:57         ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  2024-12-01 22:30           ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  2024-12-02 01:34             ` Re: CREATE SCHEMA ... CREATE DOMAIN support Michael Paquier <[email protected]>
@ 2024-12-02 02:15               ` Tom Lane <[email protected]>
  2024-12-02 16:08                 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Vik Fearing <[email protected]>
  0 siblings, 1 reply; 32+ messages in thread

From: Tom Lane @ 2024-12-02 02:15 UTC (permalink / raw)
  To: Michael Paquier <[email protected]>; +Cc: jian he <[email protected]>; Kirill Reshke <[email protected]>; Peter Eisentraut <[email protected]>; PostgreSQL Hackers <[email protected]>

Michael Paquier <[email protected]> writes:
> If I'm parsing the spec right, the doc mentions in its 5)~6) of the
> syntax rules in CREATE SCHEMA that non-schema-qualified objects should
> use the new schema name defined in the CREATE SCHEMA query.  So that
> pretty much settles the rules to use when having a new object that has
> a reference to a non-qualified object created in the same CREATE
> SCHEMA query?

I don't see where you're getting that from?  DB2 says that unqualified
reference names (not to be confused with unqualified creation-target
names) are taken to be in the new schema, but I don't see any
corresponding restriction in the spec.

What I do see (11.1 SR 6 in SQL:2021) is:

    If <schema path specification> is not specified, then a <schema
    path specification> containing an implementation-defined <schema
    name list> that contains the <schema name> contained in <schema
    name clause> is implicit.

What I read this as is that the "search path" during schema-element
creation must include at least the new schema, but can also include
some other schemas as defined by the implementation.  That makes
our behavior compliant, because we can define the other schemas
as those in the session's prevailing search_path.  (DB2's behavior
is also compliant, but they're defining the path as containing only
the new schema.)

Also, if SQL intended to constrain the search path for unqualified
identifiers to be only the new schema, they'd hardly need a concept
of <schema path specification> at all.

			regards, tom lane






^ permalink  raw  reply  [nested|flat] 32+ messages in thread

* Re: CREATE SCHEMA ... CREATE DOMAIN support
  2024-11-30 19:08 Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  2024-11-30 23:33 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  2024-12-01 05:39   ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <[email protected]>
  2024-12-01 05:53     ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  2024-12-01 09:59       ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <[email protected]>
  2024-12-01 16:57         ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  2024-12-01 22:30           ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  2024-12-02 01:34             ` Re: CREATE SCHEMA ... CREATE DOMAIN support Michael Paquier <[email protected]>
  2024-12-02 02:15               ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
@ 2024-12-02 16:08                 ` Vik Fearing <[email protected]>
  2024-12-02 16:56                   ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  0 siblings, 1 reply; 32+ messages in thread

From: Vik Fearing @ 2024-12-02 16:08 UTC (permalink / raw)
  To: Tom Lane <[email protected]>; Michael Paquier <[email protected]>; +Cc: jian he <[email protected]>; Kirill Reshke <[email protected]>; Peter Eisentraut <[email protected]>; PostgreSQL Hackers <[email protected]>


On 02/12/2024 03:15, Tom Lane wrote:
> Michael Paquier <[email protected]> writes:
>> If I'm parsing the spec right, the doc mentions in its 5)~6) of the
>> syntax rules in CREATE SCHEMA that non-schema-qualified objects should
>> use the new schema name defined in the CREATE SCHEMA query.  So that
>> pretty much settles the rules to use when having a new object that has
>> a reference to a non-qualified object created in the same CREATE
>> SCHEMA query?
> I don't see where you're getting that from?  DB2 says that unqualified
> reference names (not to be confused with unqualified creation-target
> names) are taken to be in the new schema, but I don't see any
> corresponding restriction in the spec.
>
> What I do see (11.1 SR 6 in SQL:2021) is:
>
>      If <schema path specification> is not specified, then a <schema
>      path specification> containing an implementation-defined <schema
>      name list> that contains the <schema name> contained in <schema
>      name clause> is implicit.
>
> What I read this as is that the "search path" during schema-element
> creation must include at least the new schema, but can also include
> some other schemas as defined by the implementation.  That makes
> our behavior compliant, because we can define the other schemas
> as those in the session's prevailing search_path.  (DB2's behavior
> is also compliant, but they're defining the path as containing only
> the new schema.)
>
> Also, if SQL intended to constrain the search path for unqualified
> identifiers to be only the new schema, they'd hardly need a concept
> of <schema path specification> at all.


I looked up the original paper (MUN-051) that introduced the <schema 
path specification> and it says, "The paper is proposing the use of 
paths only to resolve unqualified routine invocations."


That doesn't seem to have been explained much by the rest of the spec, 
but it is visible in the definition of <path specification> which says, 
"Specify an order for searching for an SQL-invoked routine."


I can find nowhere that says that the path can or cannot be used for 
other objects.

-- 

Vik Fearing







^ permalink  raw  reply  [nested|flat] 32+ messages in thread

* Re: CREATE SCHEMA ... CREATE DOMAIN support
  2024-11-30 19:08 Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  2024-11-30 23:33 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  2024-12-01 05:39   ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <[email protected]>
  2024-12-01 05:53     ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  2024-12-01 09:59       ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <[email protected]>
  2024-12-01 16:57         ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  2024-12-01 22:30           ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  2024-12-02 01:34             ` Re: CREATE SCHEMA ... CREATE DOMAIN support Michael Paquier <[email protected]>
  2024-12-02 02:15               ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  2024-12-02 16:08                 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Vik Fearing <[email protected]>
@ 2024-12-02 16:56                   ` Tom Lane <[email protected]>
  2024-12-02 17:26                     ` Re: CREATE SCHEMA ... CREATE DOMAIN support Vik Fearing <[email protected]>
  0 siblings, 1 reply; 32+ messages in thread

From: Tom Lane @ 2024-12-02 16:56 UTC (permalink / raw)
  To: Vik Fearing <[email protected]>; +Cc: Michael Paquier <[email protected]>; jian he <[email protected]>; Kirill Reshke <[email protected]>; Peter Eisentraut <[email protected]>; PostgreSQL Hackers <[email protected]>

Vik Fearing <[email protected]> writes:
> On 02/12/2024 03:15, Tom Lane wrote:
>> Also, if SQL intended to constrain the search path for unqualified
>> identifiers to be only the new schema, they'd hardly need a concept
>> of <schema path specification> at all.

> I looked up the original paper (MUN-051) that introduced the <schema 
> path specification> and it says, "The paper is proposing the use of 
> paths only to resolve unqualified routine invocations."

Interesting.  But still, the spec allows <schema routine> within
<schema definition>, so even that narrow interpretation opens them
to the is-this-an-external-reference-or-a-forward-reference problem.

For us, that's clouded further for functions by our overloading rules.
If foo(bigint) exists in the search path, and we have a view or
whatever that references foo() with an int argument, and there is a
CREATE FUNCTION for foo(float8) later in the <schema definition>, what
are we supposed to think is the user's intent?  (Just to save people
doing the experiment: we'd prefer foo(float8) if both are visible,
but foo(bigint) would be perfectly acceptable if not.  Other choices
of the argument types would yield different results, and none of them
seem especially open-and-shut to me.)  I don't know offhand if the
spec allows function overloading in the same way.

			regards, tom lane






^ permalink  raw  reply  [nested|flat] 32+ messages in thread

* Re: CREATE SCHEMA ... CREATE DOMAIN support
  2024-11-30 19:08 Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  2024-11-30 23:33 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  2024-12-01 05:39   ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <[email protected]>
  2024-12-01 05:53     ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  2024-12-01 09:59       ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <[email protected]>
  2024-12-01 16:57         ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  2024-12-01 22:30           ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  2024-12-02 01:34             ` Re: CREATE SCHEMA ... CREATE DOMAIN support Michael Paquier <[email protected]>
  2024-12-02 02:15               ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  2024-12-02 16:08                 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Vik Fearing <[email protected]>
  2024-12-02 16:56                   ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
@ 2024-12-02 17:26                     ` Vik Fearing <[email protected]>
  0 siblings, 0 replies; 32+ messages in thread

From: Vik Fearing @ 2024-12-02 17:26 UTC (permalink / raw)
  To: Tom Lane <[email protected]>; +Cc: Michael Paquier <[email protected]>; jian he <[email protected]>; Kirill Reshke <[email protected]>; Peter Eisentraut <[email protected]>; PostgreSQL Hackers <[email protected]>


On 02/12/2024 17:56, Tom Lane wrote:
> Vik Fearing <[email protected]> writes:
>> On 02/12/2024 03:15, Tom Lane wrote:
>>> Also, if SQL intended to constrain the search path for unqualified
>>> identifiers to be only the new schema, they'd hardly need a concept
>>> of <schema path specification> at all.
>> I looked up the original paper (MUN-051) that introduced the <schema
>> path specification> and it says, "The paper is proposing the use of
>> paths only to resolve unqualified routine invocations."
> Interesting.


The standard actually does say that that is what it is for.

Section 11.1 <schema definition> SR 8:

"The <schema name list> of the explicit or implicit <schema path 
specification> is used as the SQL- path of the schema. The SQL-path is 
used to effectively qualify unqualified <routine name>s that are 
immediately contained in <routine invocation>s that are contained in the 
<schema definition>."


> But still, the spec allows <schema routine> within
> <schema definition>, so even that narrow interpretation opens them
> to the is-this-an-external-reference-or-a-forward-reference problem.


Surely that is determined by the placement of the schema in its own 
SQL-path.


> For us, that's clouded further for functions by our overloading rules.
> If foo(bigint) exists in the search path, and we have a view or
> whatever that references foo() with an int argument, and there is a
> CREATE FUNCTION for foo(float8) later in the <schema definition>, what
> are we supposed to think is the user's intent?  (Just to save people
> doing the experiment: we'd prefer foo(float8) if both are visible,
> but foo(bigint) would be perfectly acceptable if not.  Other choices
> of the argument types would yield different results, and none of them
> seem especially open-and-shut to me.)


My answer is the same as above, for unqualified names.


However, since there is nothing that says anything either way about 
forward references, my preference is to just execute them all in the 
order written.  In your example, that would mean choosing 
otherschema.foo(bigint) over thisschema.foo(float8) if the latter hasn't 
been created yet.


> I don't know offhand if the
> spec allows function overloading in the same way.


Feature T-321 has a note saying, "Support for overloaded functions and 
procedures is not part of Core SQL."

-- 

Vik Fearing











^ permalink  raw  reply  [nested|flat] 32+ messages in thread

* Re: CREATE SCHEMA ... CREATE DOMAIN support
  2024-11-30 19:08 Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
@ 2024-12-03 19:35 ` Peter Eisentraut <[email protected]>
  2024-12-03 20:07   ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  1 sibling, 1 reply; 32+ messages in thread

From: Peter Eisentraut @ 2024-12-03 19:35 UTC (permalink / raw)
  To: Tom Lane <[email protected]>; jian he <[email protected]>; +Cc: Kirill Reshke <[email protected]>; PostgreSQL Hackers <[email protected]>

On 30.11.24 20:08, Tom Lane wrote:
> 2. transformCreateSchemaStmtElements is of the opinion that it's
> responsible for ordering the schema elements in a way that will work,
> but it just about completely fails at that task.  Ordering the objects
> by kind is surely not sufficient, and adding CREATE DOMAIN will make
> that worse.  (Example: a domain could be used in a table definition,
> but we also allow domains to be created over tables' composite types.)
> Yet we have no infrastructure that would allow us to discover the real
> dependencies between unparsed DDL commands, nor is it likely that
> anyone will ever undertake building such.  I think we ought to nuke
> that concept from orbit and just execute the schema elements in the
> order presented.  I looked at several iterations of the SQL standard
> and cannot find any support for the idea that CREATE SCHEMA needs to
> be any smarter than that.  I'd also argue that doing anything else is
> a POLA violation.  It's especially a POLA violation if the code
> rearranges a valid user-written command order into an invalid order,
> which is inevitable if we stick with the current approach.
> 
> The notion that we ought to sort the objects by kind appears to go
> all the way back to 95ef6a344 of 2002-03-21, which I guess makes it
> my fault.  There must have been some prior mailing list discussion,
> but I couldn't find much.  There is a predecessor of the committed
> patch in
> https://www.postgresql.org/message-id/flat/3C7F8A49.CC4EF0BE%40redhat.com
> but no discussion of why sorting by kind is a good idea.  (The last
> message in the thread suggests that there was more discussion among
> the Red Hat RHDB team, but if so it's lost to history now.)

SQL/Framework subclause "Descriptors" says:

"""
The execution of an SQL-statement may result in the creation of many 
descriptors. An SQL object that is created as a result of an 
SQL-statement may depend on other descriptors that are only created as a 
result of the execution of that SQL statement.

NOTE 8 — This is particularly relevant in the case of the <schema 
definition> SQL-statement. A <schema definition> can, for example, 
contain many <table definition>s that in turn contain <table 
constraint>s. A single <table constraint> in one <table definition> can 
reference a second table being created by a separate <table definition> 
which itself is able to contain a reference to the first table. The 
dependencies of each table on the descriptors of the other are valid 
provided that all necessary descriptors are created during the execution 
of the complete <schema definition>.
"""

So this says effectively that forward references are allowed.  Whether 
reordering the statements is a good way to implement that is dubious, as 
we are discovering.






^ permalink  raw  reply  [nested|flat] 32+ messages in thread

* Re: CREATE SCHEMA ... CREATE DOMAIN support
  2024-11-30 19:08 Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  2024-12-03 19:35 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Peter Eisentraut <[email protected]>
@ 2024-12-03 20:07   ` Tom Lane <[email protected]>
  2024-12-11 06:22     ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <[email protected]>
  0 siblings, 1 reply; 32+ messages in thread

From: Tom Lane @ 2024-12-03 20:07 UTC (permalink / raw)
  To: Peter Eisentraut <[email protected]>; +Cc: jian he <[email protected]>; Kirill Reshke <[email protected]>; PostgreSQL Hackers <[email protected]>

Peter Eisentraut <[email protected]> writes:
> On 30.11.24 20:08, Tom Lane wrote:
>> ... I think we ought to nuke
>> that concept from orbit and just execute the schema elements in the
>> order presented.  I looked at several iterations of the SQL standard
>> and cannot find any support for the idea that CREATE SCHEMA needs to
>> be any smarter than that.

> SQL/Framework subclause "Descriptors" says:

> """
> The execution of an SQL-statement may result in the creation of many 
> descriptors. An SQL object that is created as a result of an 
> SQL-statement may depend on other descriptors that are only created as a 
> result of the execution of that SQL statement.

> NOTE 8 — This is particularly relevant in the case of the <schema 
> definition> SQL-statement. A <schema definition> can, for example, 
> contain many <table definition>s that in turn contain <table 
> constraint>s. A single <table constraint> in one <table definition> can 
> reference a second table being created by a separate <table definition> 
> which itself is able to contain a reference to the first table. The 
> dependencies of each table on the descriptors of the other are valid 
> provided that all necessary descriptors are created during the execution 
> of the complete <schema definition>.
> """

Ah, thanks for the pointer.

> So this says effectively that forward references are allowed.  Whether 
> reordering the statements is a good way to implement that is dubious, as 
> we are discovering.

Yeah, I think it's a long way from this text to the conclusion that
the implementation is responsible for reordering the subcommands
to remove forward references.  And it really offers no help at all
for the ensuing problem of distinguishing forward references from
external references.

The one aspect of the spec's definition that seems useful to me in
practice is the ability to create quasi-circular foreign keys (that
is, t1 has an FK to t2 and t2 has an FK to t1).  But that is something
we have never implemented in 22 years and nobody has complained of
the lack.  I'm totally willing to throw that possibility overboard
permanently in order to expand the set of creatable object types
without introducing a ton of restrictions and weird behaviors.
What do you think?

			regards, tom lane

PS: if we were really excited about allowing circular FKs to be
made within CREATE SCHEMA, a possible though non-standard answer
would be to allow ALTER TABLE ADD CONSTRAINT as a <schema element>.






^ permalink  raw  reply  [nested|flat] 32+ messages in thread

* Re: CREATE SCHEMA ... CREATE DOMAIN support
  2024-11-30 19:08 Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  2024-12-03 19:35 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Peter Eisentraut <[email protected]>
  2024-12-03 20:07   ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
@ 2024-12-11 06:22     ` Kirill Reshke <[email protected]>
  2024-12-11 17:08       ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  0 siblings, 1 reply; 32+ messages in thread

From: Kirill Reshke @ 2024-12-11 06:22 UTC (permalink / raw)
  To: Tom Lane <[email protected]>; +Cc: Peter Eisentraut <[email protected]>; jian he <[email protected]>; PostgreSQL Hackers <[email protected]>

Looks like this thread does not move forward. So I'm posting my
thoughts to bump it.


On Wed, 4 Dec 2024 at 01:07, Tom Lane <[email protected]> wrote:
> I'm totally willing to throw that possibility overboard
> permanently in order to expand the set of creatable object types
> without introducing a ton of restrictions and weird behaviors.
> What do you think?

Im +1 on this, but can you please elaborate, which exact objects
cannot be created now? What will be expanded after
v2-0002-Dont_try-to-reoder....?

> PS: if we were really excited about allowing circular FKs to be
> made within CREATE SCHEMA, a possible though non-standard answer
> would be to allow ALTER TABLE ADD CONSTRAINT as a <schema element>.

That's a nice feature to have by itself?


-- 
Best regards,
Kirill Reshke






^ permalink  raw  reply  [nested|flat] 32+ messages in thread

* Re: CREATE SCHEMA ... CREATE DOMAIN support
  2024-11-30 19:08 Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  2024-12-03 19:35 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Peter Eisentraut <[email protected]>
  2024-12-03 20:07   ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  2024-12-11 06:22     ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <[email protected]>
@ 2024-12-11 17:08       ` Tom Lane <[email protected]>
  2025-08-19 03:36         ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <[email protected]>
  0 siblings, 1 reply; 32+ messages in thread

From: Tom Lane @ 2024-12-11 17:08 UTC (permalink / raw)
  To: Kirill Reshke <[email protected]>; +Cc: Peter Eisentraut <[email protected]>; jian he <[email protected]>; PostgreSQL Hackers <[email protected]>

Kirill Reshke <[email protected]> writes:
> On Wed, 4 Dec 2024 at 01:07, Tom Lane <[email protected]> wrote:
>> I'm totally willing to throw that possibility overboard
>> permanently in order to expand the set of creatable object types
>> without introducing a ton of restrictions and weird behaviors.
>> What do you think?

> Im +1 on this, but can you please elaborate, which exact objects
> cannot be created now? What will be expanded after
> v2-0002-Dont_try-to-reoder....?

The problem is not too awful right now, because of the very limited
set of object types that CREATE SCHEMA supports.  The only case
I can think of offhand is a table referencing a view's rowtype,
for example

   create schema s1
	create view v1 as select ...
	create table t1 (compositecol v1, ...);

Since transformCreateSchemaStmtElements re-orders views after
tables, this'll fail, and there is no way to fix that except
by giving up use of the elements-in-CREATE-SCHEMA feature.
Admittedly it's a strange usage, and probably no one has tried it.

However, once we start adding in data types and functions,
the hazard grows substantially, because there are more usage
patterns and they can't all be satisfied by a simple object-type
ordering.  For example, domains are already enough to cause
trouble, because we allow domains over composites:

   create schema s1
	create table t1 (...)
	create domain d1 as t1 check(...);

Re-ordering domains before tables would break this case, but
the other order has other problems.  Looking a bit further
down the road, how would you handle creation of a base type
within CREATE SCHEMA?

   create schema s1
	create type myscalar
	create function myscalar_in(cstring) returns myscalar ...
	create function myscalar_out(myscalar) returns cstring ...
	create type myscalar (input = myscalar_in, ...);

This cannot possibly work if an object-type-based re-ordering
is done to it.

So IMV, we have three possibilities:

1. CREATE SCHEMA's schema-element feature remains forevermore
a sad joke that (a) doesn't cover nearly enough to be useful and
(b) doesn't come close to doing what the spec says it should.

2. We invest an enormous amount of engineering effort on trying
to extract dependencies from not-yet-analyzed parse trees, after
which we invest a bunch more effort figuring out heuristics for
ordering the subcommands in the face of circular dependencies.
(Some of that could be stolen from pg_dump, but not all: pg_dump
only has to resolve a limited set of cases.)

3. We bypass the need for #2 by decreeing that we'll execute
the subcommands in order.


>> PS: if we were really excited about allowing circular FKs to be
>> made within CREATE SCHEMA, a possible though non-standard answer
>> would be to allow ALTER TABLE ADD CONSTRAINT as a <schema element>.

> That's a nice feature to have by itself?

Not unless we abandon the idea of subcommand reordering, because
where are you going to put the ALTER TABLE subcommands?

			regards, tom lane






^ permalink  raw  reply  [nested|flat] 32+ messages in thread

* Re: CREATE SCHEMA ... CREATE DOMAIN support
  2024-11-30 19:08 Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  2024-12-03 19:35 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Peter Eisentraut <[email protected]>
  2024-12-03 20:07   ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  2024-12-11 06:22     ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <[email protected]>
  2024-12-11 17:08       ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
@ 2025-08-19 03:36         ` jian he <[email protected]>
  2025-08-22 08:59           ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <[email protected]>
  0 siblings, 1 reply; 32+ messages in thread

From: jian he @ 2025-08-19 03:36 UTC (permalink / raw)
  To: Tom Lane <[email protected]>; +Cc: Kirill Reshke <[email protected]>; Peter Eisentraut <[email protected]>; PostgreSQL Hackers <[email protected]>

On Thu, Dec 12, 2024 at 1:08 AM Tom Lane <[email protected]> wrote:
>
> The problem is not too awful right now, because of the very limited
> set of object types that CREATE SCHEMA supports.  The only case
> I can think of offhand is a table referencing a view's rowtype,
> for example
>
>    create schema s1
>         create view v1 as select ...
>         create table t1 (compositecol v1, ...);
>
> Since transformCreateSchemaStmtElements re-orders views after
> tables, this'll fail, and there is no way to fix that except
> by giving up use of the elements-in-CREATE-SCHEMA feature.
> Admittedly it's a strange usage, and probably no one has tried it.
>
> However, once we start adding in data types and functions,
> the hazard grows substantially, because there are more usage
> patterns and they can't all be satisfied by a simple object-type
> ordering.  For example, domains are already enough to cause
> trouble, because we allow domains over composites:
>
>    create schema s1
>         create table t1 (...)
>         create domain d1 as t1 check(...);
>
> Re-ordering domains before tables would break this case, but
> the other order has other problems.  Looking a bit further
> down the road, how would you handle creation of a base type
> within CREATE SCHEMA?
>
>    create schema s1
>         create type myscalar
>         create function myscalar_in(cstring) returns myscalar ...
>         create function myscalar_out(myscalar) returns cstring ...
>         create type myscalar (input = myscalar_in, ...);
>
> This cannot possibly work if an object-type-based re-ordering
> is done to it.
>
> So IMV, we have three possibilities:
>
> 1. CREATE SCHEMA's schema-element feature remains forevermore
> a sad joke that (a) doesn't cover nearly enough to be useful and
> (b) doesn't come close to doing what the spec says it should.
>
> 2. We invest an enormous amount of engineering effort on trying
> to extract dependencies from not-yet-analyzed parse trees, after
> which we invest a bunch more effort figuring out heuristics for
> ordering the subcommands in the face of circular dependencies.
> (Some of that could be stolen from pg_dump, but not all: pg_dump
> only has to resolve a limited set of cases.)
>
> 3. We bypass the need for #2 by decreeing that we'll execute
> the subcommands in order.
>
>
> >> PS: if we were really excited about allowing circular FKs to be
> >> made within CREATE SCHEMA, a possible though non-standard answer
> >> would be to allow ALTER TABLE ADD CONSTRAINT as a <schema element>.
>
> > That's a nice feature to have by itself?
>
> Not unless we abandon the idea of subcommand reordering, because
> where are you going to put the ALTER TABLE subcommands?
>

hi.
move this forward with option #3 (executing the subcommands in order).
pg_dump don't use CREATE SCHEMA ...CREATE ...
so if we error out
CREATE SCHEMA regress_schema_2 CREATE VIEW abcd_view AS SELECT a FROM
abcd CREATE TABLE abcd (a int);
it won't be a big compatibility issue?
Also this thread doesn’t show strong support for sorting the subcommands.

the full <schema definition> in 11.1 is:
11.1 <schema definition>

<schema element> ::=
<table definition>
| <view definition>
| <domain definition>
| <character set definition>
| <collation definition>
| <transliteration definition>
| <assertion definition>
| <trigger definition>
| <user-defined type definition>
| <user-defined cast definition>
| <user-defined ordering definition>
| <transform definition>
| <schema routine>
| <sequence generator definition>
| <grant statement>
| <role definition>

so I also add support for CREATE SCHEMA CREATE COLLATION.

v6-0001-Don-t-try-to-re-order-the-subcommands-of-CREATE-SCHEMA.patch
v6-0002-CREATE-SCHEMA-CREATE-DOMAIN.patch
v6-0003-CREATE-SCHEMA-CREATE-COLLATION.patch

v6-0001-Don-t-try-to-re-order-the-subcommands-of-CREATE-SCHEMA.patch
is refactor/rebase based on
v1-0001-Don-t-try-to-re-order-the-subcommands-of-CREATE-S.patch

v6-0002-CREATE-SCHEMA-CREATE-DOMAIN.patch
for CREATE SCHEMA ... CREATE-DOMAIN

v6-0003-CREATE-SCHEMA-CREATE-COLLATION.patch
for CREATE SCHEMA ... CREATE-COLLATION


Attachments:

  [text/x-patch] v6-0002-CREATE-SCHEMA-CREATE-DOMAIN.patch (12.3K, 2-v6-0002-CREATE-SCHEMA-CREATE-DOMAIN.patch)
  download | inline diff:
From 162ca0e712a3bbe16193275104e7c52739633503 Mon Sep 17 00:00:00 2001
From: jian he <[email protected]>
Date: Mon, 18 Aug 2025 16:28:37 +0800
Subject: [PATCH v6 2/3] CREATE SCHEMA CREATE DOMAIN

SQL standard allow domain to be specified with CREATE SCHEMA
statement. This patch adds support in PostgreSQL for that.

For example:
    CREATE SCHEMA schema_name AUTHORIZATION CURRENT_ROLE
    create domain ss as text not null;

The domain will be created within the to be created schema.
The domain name can be schema-qualified or database-qualified,
however it's not allowed to let domain create within a different schema.

Author: Kirill Reshke <[email protected]>
Author: Jian He <[email protected]>
Reviewed-by: Alvaro Herrera <[email protected]>
Reviewed-by: Tom Lane <[email protected]>

Discussion: https://postgr.es/m/CALdSSPh4jUSDsWu3K58hjO60wnTRR0DuO4CKRcwa8EVuOSfXxg@mail.gmail.com
---
 doc/src/sgml/ref/create_schema.sgml           |  4 +-
 src/backend/parser/gram.y                     |  1 +
 src/backend/parser/parse_utilcmd.c            | 25 +++++++++
 src/bin/psql/tab-complete.in.c                | 12 ++--
 .../expected/create_schema.out                |  4 +-
 .../test_ddl_deparse/sql/create_schema.sql    |  3 +-
 src/test/regress/expected/create_schema.out   | 56 +++++++++++++++++++
 src/test/regress/sql/create_schema.sql        | 35 ++++++++++++
 8 files changed, 130 insertions(+), 10 deletions(-)

diff --git a/doc/src/sgml/ref/create_schema.sgml b/doc/src/sgml/ref/create_schema.sgml
index 625793a6b67..79186d2b936 100644
--- a/doc/src/sgml/ref/create_schema.sgml
+++ b/doc/src/sgml/ref/create_schema.sgml
@@ -100,8 +100,8 @@ CREATE SCHEMA IF NOT EXISTS AUTHORIZATION <replaceable class="parameter">role_sp
       <listitem>
        <para>
         An SQL statement defining an object to be created within the
-        schema. Currently, only <command>CREATE
-        TABLE</command>, <command>CREATE VIEW</command>, <command>CREATE
+        schema. Currently, only <command>CREATE DOMAIN</command>
+        <command>CREATE TABLE</command>, <command>CREATE VIEW</command>, <command>CREATE
         INDEX</command>, <command>CREATE SEQUENCE</command>, <command>CREATE
         TRIGGER</command> and <command>GRANT</command> are accepted as clauses
         within <command>CREATE SCHEMA</command>. Other kinds of objects may
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index db43034b9db..3aca508b08f 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -1626,6 +1626,7 @@ schema_stmt:
 			| CreateTrigStmt
 			| GrantStmt
 			| ViewStmt
+			| CreateDomainStmt
 		;
 
 
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 74672a458d5..218ec6f0982 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -4146,6 +4146,31 @@ transformCreateSchemaStmtElements(ParseState *pstate, List *schemaElts,
 				elements = lappend(elements, element);
 				break;
 
+			case T_CreateDomainStmt:
+				{
+					char	   *domain_schema = NULL;
+					CreateDomainStmt *elp = (CreateDomainStmt *) element;
+
+					/*
+					 * The schema of the DOMAIN must match the schema being created.
+					 * If the domain name length exceeds 3, it will fail in
+					 * DeconstructQualifiedName.
+					*/
+					if (list_length(elp->domainname) == 2)
+						domain_schema = strVal(list_nth(elp->domainname, 0));
+					else if (list_length(elp->domainname) == 3)
+						domain_schema = strVal(list_nth(elp->domainname, 1));
+
+					if (domain_schema != NULL && strcmp(domain_schema, schemaName) != 0)
+						ereport(ERROR,
+								errcode(ERRCODE_INVALID_SCHEMA_DEFINITION),
+								errmsg("CREATE specifies a schema (%s) "
+									   "different from the one being created (%s)",
+									   schemaName, domain_schema));
+					elements = lappend(elements, element);
+				}
+				break;
+
 			default:
 				elog(ERROR, "unrecognized node type: %d",
 					 (int) nodeTag(element));
diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c
index 8b10f2313f3..d7a8c769e35 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -2178,7 +2178,7 @@ match_previous_words(int pattern_id,
 	{
 		/* only some object types can be created as part of CREATE SCHEMA */
 		if (HeadMatches("CREATE", "SCHEMA"))
-			COMPLETE_WITH("TABLE", "VIEW", "INDEX", "SEQUENCE", "TRIGGER",
+			COMPLETE_WITH("TABLE", "VIEW", "INDEX", "SEQUENCE", "TRIGGER", "DOMAIN",
 			/* for INDEX and TABLE/SEQUENCE, respectively */
 						  "UNIQUE", "UNLOGGED");
 		else
@@ -3399,15 +3399,15 @@ match_previous_words(int pattern_id,
 	else if (Matches("CREATE", "DATABASE", MatchAny, "STRATEGY"))
 		COMPLETE_WITH("WAL_LOG", "FILE_COPY");
 
-	/* CREATE DOMAIN */
-	else if (Matches("CREATE", "DOMAIN", MatchAny))
+	/* CREATE DOMAIN --- is allowed inside CREATE SCHEMA, so use TailMatches */
+	else if (TailMatches("CREATE", "DOMAIN", MatchAny))
 		COMPLETE_WITH("AS");
-	else if (Matches("CREATE", "DOMAIN", MatchAny, "AS"))
+	else if (TailMatches("CREATE", "DOMAIN", MatchAny, "AS"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes);
-	else if (Matches("CREATE", "DOMAIN", MatchAny, "AS", MatchAny))
+	else if (TailMatches("CREATE", "DOMAIN", MatchAny, "AS", MatchAny))
 		COMPLETE_WITH("COLLATE", "DEFAULT", "CONSTRAINT",
 					  "NOT NULL", "NULL", "CHECK (");
-	else if (Matches("CREATE", "DOMAIN", MatchAny, "COLLATE"))
+	else if (TailMatches("CREATE", "DOMAIN", MatchAny, "COLLATE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_collations);
 
 	/* CREATE EXTENSION */
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..d73c4702051 100644
--- a/src/test/modules/test_ddl_deparse/expected/create_schema.out
+++ b/src/test/modules/test_ddl_deparse/expected/create_schema.out
@@ -13,7 +13,9 @@ CREATE SCHEMA IF NOT EXISTS baz;
 NOTICE:  schema "baz" already exists, skipping
 CREATE SCHEMA element_test
   CREATE TABLE foo (id int)
-  CREATE VIEW bar AS SELECT * FROM foo;
+  CREATE VIEW bar AS SELECT * FROM foo
+  CREATE DOMAIN d1 AS INT;
 NOTICE:  DDL test: type simple, tag CREATE SCHEMA
 NOTICE:  DDL test: type simple, tag CREATE TABLE
 NOTICE:  DDL test: type simple, tag CREATE VIEW
+NOTICE:  DDL test: type simple, tag CREATE DOMAIN
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..57ada462070 100644
--- a/src/test/modules/test_ddl_deparse/sql/create_schema.sql
+++ b/src/test/modules/test_ddl_deparse/sql/create_schema.sql
@@ -14,4 +14,5 @@ CREATE SCHEMA IF NOT EXISTS baz;
 
 CREATE SCHEMA element_test
   CREATE TABLE foo (id int)
-  CREATE VIEW bar AS SELECT * FROM foo;
+  CREATE VIEW bar AS SELECT * FROM foo
+  CREATE DOMAIN d1 AS INT;
diff --git a/src/test/regress/expected/create_schema.out b/src/test/regress/expected/create_schema.out
index 38530c282a9..d6718a9f519 100644
--- a/src/test/regress/expected/create_schema.out
+++ b/src/test/regress/expected/create_schema.out
@@ -131,5 +131,61 @@ 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;
+-- Cases where the schema creation with domain.
+--fail. cannot CREATE DOMAIN to other schema
+CREATE SCHEMA regress_schema_2 AUTHORIZATION CURRENT_ROLE
+  CREATE DOMAIN public.ss AS TEXT NOT NULL DEFAULT 'hello' CONSTRAINT nn CHECK (VALUE <> 'hello')
+  CREATE TABLE t(a ss);
+ERROR:  CREATE specifies a schema (regress_schema_2) different from the one being created (public)
+--fail. cannot CREATE DOMAIN to other schema
+CREATE SCHEMA regress_schema_2 AUTHORIZATION CURRENT_ROLE
+  CREATE DOMAIN postgres.public.ss AS TEXT NOT NULL CONSTRAINT nn CHECK (VALUE <> 'hello') DEFAULT 'hello'
+  CREATE TABLE t(a ss);
+ERROR:  CREATE specifies a schema (regress_schema_2) different from the one being created (public)
+--fail. improper qualified name
+CREATE SCHEMA regress_schema_2 CREATE DOMAIN ss.postgres.regress_schema_2.ss AS TEXT;
+ERROR:  improper qualified name (too many dotted names): ss.postgres.regress_schema_2.ss
+--fail. Execute subcommands in order; we do not implicitly reorder them.
+CREATE SCHEMA regress_schema_2 AUTHORIZATION CURRENT_ROLE
+  CREATE DOMAIN ss1 AS ss
+  CREATE DOMAIN ss AS TEXT;
+ERROR:  type "ss" does not exist
+LINE 2:   CREATE DOMAIN ss1 AS ss
+                               ^
+--ok, qualified schema name for domain should be same as the created schema.
+CREATE SCHEMA regress_schema_2 AUTHORIZATION CURRENT_ROLE
+  CREATE DOMAIN regress_schema_2.ss AS TEXT NOT NULL CONSTRAINT nn CHECK (VALUE <> 'hello') DEFAULT 'hello' COLLATE "C"
+  CREATE TABLE t(a regress_schema_2.ss);
+\dD regress_schema_2.*
+                                            List of domains
+      Schema      | Name | Type | Collation | Nullable |    Default    |             Check              
+------------------+------+------+-----------+----------+---------------+--------------------------------
+ regress_schema_2 | ss   | text | C         | not null | 'hello'::text | CHECK (VALUE <> 'hello'::text)
+(1 row)
+
+--ok, no qualified schema name for domain.
+CREATE SCHEMA regress_schema_3 AUTHORIZATION CURRENT_ROLE
+  CREATE DOMAIN ss AS TEXT  CONSTRAINT nn CHECK (VALUE <> 'hello') NOT NULL DEFAULT 'hello'
+  CREATE DOMAIN ss1 AS ss
+  CREATE VIEW test AS SELECT 'hello'::ss AS test
+  CREATE TABLE t(a ss1);
+\dD regress_schema_3.*
+                                                    List of domains
+      Schema      | Name |        Type         | Collation | Nullable |    Default    |             Check              
+------------------+------+---------------------+-----------+----------+---------------+--------------------------------
+ regress_schema_3 | ss   | text                |           | not null | 'hello'::text | CHECK (VALUE <> 'hello'::text)
+ regress_schema_3 | ss1  | regress_schema_3.ss |           |          | 'hello'::text | 
+(2 rows)
+
+DROP SCHEMA regress_schema_2 CASCADE;
+NOTICE:  drop cascades to 2 other objects
+DETAIL:  drop cascades to type regress_schema_2.ss
+drop cascades to table regress_schema_2.t
+DROP SCHEMA regress_schema_3 CASCADE;
+NOTICE:  drop cascades to 4 other objects
+DETAIL:  drop cascades to type regress_schema_3.ss
+drop cascades to type regress_schema_3.ss1
+drop cascades to view regress_schema_3.test
+drop cascades to table regress_schema_3.t
 -- Clean up
 DROP ROLE regress_create_schema_role;
diff --git a/src/test/regress/sql/create_schema.sql b/src/test/regress/sql/create_schema.sql
index b3dc1cfd758..3028148e96b 100644
--- a/src/test/regress/sql/create_schema.sql
+++ b/src/test/regress/sql/create_schema.sql
@@ -71,5 +71,40 @@ CREATE SCHEMA regress_schema_1 AUTHORIZATION CURRENT_ROLE
 DROP SCHEMA regress_schema_1 CASCADE;
 RESET ROLE;
 
+-- Cases where the schema creation with domain.
+--fail. cannot CREATE DOMAIN to other schema
+CREATE SCHEMA regress_schema_2 AUTHORIZATION CURRENT_ROLE
+  CREATE DOMAIN public.ss AS TEXT NOT NULL DEFAULT 'hello' CONSTRAINT nn CHECK (VALUE <> 'hello')
+  CREATE TABLE t(a ss);
+--fail. cannot CREATE DOMAIN to other schema
+CREATE SCHEMA regress_schema_2 AUTHORIZATION CURRENT_ROLE
+  CREATE DOMAIN postgres.public.ss AS TEXT NOT NULL CONSTRAINT nn CHECK (VALUE <> 'hello') DEFAULT 'hello'
+  CREATE TABLE t(a ss);
+
+--fail. improper qualified name
+CREATE SCHEMA regress_schema_2 CREATE DOMAIN ss.postgres.regress_schema_2.ss AS TEXT;
+
+--fail. Execute subcommands in order; we do not implicitly reorder them.
+CREATE SCHEMA regress_schema_2 AUTHORIZATION CURRENT_ROLE
+  CREATE DOMAIN ss1 AS ss
+  CREATE DOMAIN ss AS TEXT;
+
+--ok, qualified schema name for domain should be same as the created schema.
+CREATE SCHEMA regress_schema_2 AUTHORIZATION CURRENT_ROLE
+  CREATE DOMAIN regress_schema_2.ss AS TEXT NOT NULL CONSTRAINT nn CHECK (VALUE <> 'hello') DEFAULT 'hello' COLLATE "C"
+  CREATE TABLE t(a regress_schema_2.ss);
+\dD regress_schema_2.*
+
+--ok, no qualified schema name for domain.
+CREATE SCHEMA regress_schema_3 AUTHORIZATION CURRENT_ROLE
+  CREATE DOMAIN ss AS TEXT  CONSTRAINT nn CHECK (VALUE <> 'hello') NOT NULL DEFAULT 'hello'
+  CREATE DOMAIN ss1 AS ss
+  CREATE VIEW test AS SELECT 'hello'::ss AS test
+  CREATE TABLE t(a ss1);
+\dD regress_schema_3.*
+
+DROP SCHEMA regress_schema_2 CASCADE;
+DROP SCHEMA regress_schema_3 CASCADE;
+
 -- Clean up
 DROP ROLE regress_create_schema_role;
-- 
2.34.1



  [text/x-patch] v6-0001-Don-t-try-to-re-order-the-subcommands-of-CREATE-SCHEMA.patch (26.3K, 3-v6-0001-Don-t-try-to-re-order-the-subcommands-of-CREATE-SCHEMA.patch)
  download | inline diff:
From 22bd3d92986b657d2a6a5dd0f8e4d6c2485796b0 Mon Sep 17 00:00:00 2001
From: Tom Lane <[email protected]>
Date: Tue, 19 Aug 2025 10:52:05 +0800
Subject: [PATCH v6 1/3] Don't try to re-order the subcommands of CREATE SCHEMA
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

transformCreateSchemaStmtElements has always believed that it is
supposed to re-order the subcommands of CREATE SCHEMA into a safe
execution order.  However, it is nowhere near being capable of doing
that correctly.  Nor is there reason to think that it ever will be,
or that that is a well-defined requirement, or that there's any basis
in the SQL standard for it.  Moreover, the problem will get worse as
we add more subcommand types.  Let's just drop the whole idea and
execute the commands in the order given, which seems like a much less
astonishment-prone definition anyway.

Along the way, pass down a ParseState so that we can provide an
error cursor for the "wrong schema name" error, and fix
transformCreateSchemaStmtElements so that it doesn't scribble
on the parsetree passed to it.

Note: This will cause compatibility issue,
for example:
CREATE SCHEMA regress_schema_2
  CREATE VIEW abcd_view AS SELECT a FROM abcd
  CREATE TABLE abcd (a int);

With the patch, it will throw an error, whereas on HEAD it won’t.

Discussion: https://postgr.es/m/[email protected]
---
 doc/src/sgml/ref/create_schema.sgml         |  10 +-
 src/backend/commands/extension.c            |   7 +-
 src/backend/commands/schemacmds.c           |  15 ++-
 src/backend/parser/parse_utilcmd.c          | 135 ++++++++------------
 src/backend/tcop/utility.c                  |   3 +-
 src/include/commands/schemacmds.h           |   7 +-
 src/include/parser/parse_utilcmd.h          |   3 +-
 src/test/regress/expected/create_schema.out |  37 ++++++
 src/test/regress/expected/create_view.out   |   2 +
 src/test/regress/expected/event_trigger.out |   2 +-
 src/test/regress/expected/namespace.out     |   9 +-
 src/test/regress/sql/create_schema.sql      |   5 +
 src/test/regress/sql/namespace.sql          |  11 +-
 src/tools/pgindent/typedefs.list            |   1 -
 14 files changed, 132 insertions(+), 115 deletions(-)

diff --git a/doc/src/sgml/ref/create_schema.sgml b/doc/src/sgml/ref/create_schema.sgml
index ed69298ccc6..625793a6b67 100644
--- a/doc/src/sgml/ref/create_schema.sgml
+++ b/doc/src/sgml/ref/create_schema.sgml
@@ -193,12 +193,10 @@ CREATE VIEW hollywood.winners AS
   </para>
 
   <para>
-   The SQL standard specifies that the subcommands in <command>CREATE
-   SCHEMA</command> can appear in any order.  The present
-   <productname>PostgreSQL</productname> implementation does not
-   handle all cases of forward references in subcommands; it might
-   sometimes be necessary to reorder the subcommands in order to avoid
-   forward references.
+   <productname>PostgreSQL</productname> executes the subcommands
+   in <command>CREATE SCHEMA</command> in the order given.  Other
+   implementations may try to rearrange the subcommands into dependency
+   order, but that is hard if not impossible to do correctly.
   </para>
 
   <para>
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index e6f9ab6dfd6..ad578bc76f7 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1773,14 +1773,17 @@ CreateExtensionInternal(char *extensionName,
 
 		if (!OidIsValid(schemaOid))
 		{
+			ParseState *pstate = make_parsestate(NULL);
 			CreateSchemaStmt *csstmt = makeNode(CreateSchemaStmt);
 
+			pstate->p_sourcetext = "(generated CREATE SCHEMA command)";
+
 			csstmt->schemaname = schemaName;
 			csstmt->authrole = NULL;	/* will be created by current user */
 			csstmt->schemaElts = NIL;
 			csstmt->if_not_exists = false;
-			CreateSchemaCommand(csstmt, "(generated CREATE SCHEMA command)",
-								-1, -1);
+
+			CreateSchemaCommand(pstate, csstmt, -1, -1);
 
 			/*
 			 * CreateSchemaCommand includes CommandCounterIncrement, so new
diff --git a/src/backend/commands/schemacmds.c b/src/backend/commands/schemacmds.c
index 0f03d9743d2..b32935215f9 100644
--- a/src/backend/commands/schemacmds.c
+++ b/src/backend/commands/schemacmds.c
@@ -49,7 +49,7 @@ static void AlterSchemaOwner_internal(HeapTuple tup, Relation rel, Oid newOwnerI
  * a subquery.
  */
 Oid
-CreateSchemaCommand(CreateSchemaStmt *stmt, const char *queryString,
+CreateSchemaCommand(ParseState *pstate, CreateSchemaStmt *stmt,
 					int stmt_location, int stmt_len)
 {
 	const char *schemaName = stmt->schemaname;
@@ -189,12 +189,13 @@ CreateSchemaCommand(CreateSchemaStmt *stmt, const char *queryString,
 
 	/*
 	 * Examine the list of commands embedded in the CREATE SCHEMA command, and
-	 * reorganize them into a sequentially executable order with no forward
-	 * references.  Note that the result is still a list of raw parsetrees ---
-	 * we cannot, in general, run parse analysis on one statement until we
-	 * have actually executed the prior ones.
+	 * do preliminary transformations (mostly, verify that none are trying to
+	 * create objects outside the new schema).  Note that the result is still
+	 * a list of raw parsetrees --- we cannot, in general, run parse analysis
+	 * on one statement until we have actually executed the prior ones.
 	 */
-	parsetree_list = transformCreateSchemaStmtElements(stmt->schemaElts,
+	parsetree_list = transformCreateSchemaStmtElements(pstate,
+													   stmt->schemaElts,
 													   schemaName);
 
 	/*
@@ -219,7 +220,7 @@ CreateSchemaCommand(CreateSchemaStmt *stmt, const char *queryString,
 
 		/* do this step */
 		ProcessUtility(wrapper,
-					   queryString,
+					   pstate->p_sourcetext,
 					   false,
 					   PROCESS_UTILITY_SUBCOMMAND,
 					   NULL,
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..74672a458d5 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -95,18 +95,6 @@ typedef struct
 	bool		ofType;			/* true if statement contains OF typename */
 } CreateStmtContext;
 
-/* State shared by transformCreateSchemaStmtElements and its subroutines */
-typedef struct
-{
-	const char *schemaname;		/* name of schema */
-	List	   *sequences;		/* CREATE SEQUENCE items */
-	List	   *tables;			/* CREATE TABLE items */
-	List	   *views;			/* CREATE VIEW items */
-	List	   *indexes;		/* CREATE INDEX items */
-	List	   *triggers;		/* CREATE TRIGGER items */
-	List	   *grants;			/* GRANT items */
-} CreateSchemaStmtContext;
-
 
 static void transformColumnDefinition(CreateStmtContext *cxt,
 									  ColumnDef *column);
@@ -133,7 +121,8 @@ static void transformCheckConstraints(CreateStmtContext *cxt,
 static void transformConstraintAttrs(CreateStmtContext *cxt,
 									 List *constraintList);
 static void transformColumnType(CreateStmtContext *cxt, ColumnDef *column);
-static void setSchemaName(const char *context_schema, char **stmt_schema_name);
+static void checkSchemaName(ParseState *pstate, const char *context_schema,
+							RangeVar *relation);
 static void transformPartitionCmd(CreateStmtContext *cxt, PartitionCmd *cmd);
 static List *transformPartitionRangeBounds(ParseState *pstate, List *blist,
 										   Relation parent);
@@ -4076,51 +4065,35 @@ transformColumnType(CreateStmtContext *cxt, ColumnDef *column)
  * transformCreateSchemaStmtElements -
  *	  analyzes the elements of a CREATE SCHEMA statement
  *
- * Split the schema element list from a CREATE SCHEMA statement into
- * individual commands and place them in the result list in an order
- * such that there are no forward references (e.g. GRANT to a table
- * created later in the list). Note that the logic we use for determining
- * forward references is presently quite incomplete.
+ * 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.
+ * 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.
  *
  * "schemaName" is the name of the schema that will be used for the creation
- * of the objects listed, that may be compiled from the schema name defined
+ * of the objects listed.  It may be obtained from the schema name defined
  * in the statement or a role specification.
  *
- * SQL also allows constraints to make forward references, so thumb through
- * the table columns and move forward references to a posterior alter-table
- * command.
- *
  * 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: this breaks the rules a little bit by modifying schema-name fields
- * within passed-in structs.  However, the transformation would be the same
- * if done over, so it should be all right to scribble on the input to this
- * extent.
  */
 List *
-transformCreateSchemaStmtElements(List *schemaElts, const char *schemaName)
+transformCreateSchemaStmtElements(ParseState *pstate, List *schemaElts,
+								  const char *schemaName)
 {
-	CreateSchemaStmtContext cxt;
-	List	   *result;
-	ListCell   *elements;
-
-	cxt.schemaname = schemaName;
-	cxt.sequences = NIL;
-	cxt.tables = NIL;
-	cxt.views = NIL;
-	cxt.indexes = NIL;
-	cxt.triggers = NIL;
-	cxt.grants = NIL;
+	List	   *elements = NIL;
+	ListCell   *lc;
 
 	/*
-	 * Run through each schema element in the schema element list. Separate
-	 * statements by type, and do preliminary analysis.
+	 * Run through each schema element in the schema element list.  Check
+	 * target schema names, and collect the list of actions to be done.
 	 */
-	foreach(elements, schemaElts)
+	foreach(lc, schemaElts)
 	{
-		Node	   *element = lfirst(elements);
+		Node	   *element = lfirst(lc);
 
 		switch (nodeTag(element))
 		{
@@ -4128,8 +4101,8 @@ transformCreateSchemaStmtElements(List *schemaElts, const char *schemaName)
 				{
 					CreateSeqStmt *elp = (CreateSeqStmt *) element;
 
-					setSchemaName(cxt.schemaname, &elp->sequence->schemaname);
-					cxt.sequences = lappend(cxt.sequences, element);
+					checkSchemaName(pstate, schemaName, elp->sequence);
+					elements = lappend(elements, element);
 				}
 				break;
 
@@ -4137,12 +4110,8 @@ transformCreateSchemaStmtElements(List *schemaElts, const char *schemaName)
 				{
 					CreateStmt *elp = (CreateStmt *) element;
 
-					setSchemaName(cxt.schemaname, &elp->relation->schemaname);
-
-					/*
-					 * XXX todo: deal with constraints
-					 */
-					cxt.tables = lappend(cxt.tables, element);
+					checkSchemaName(pstate, schemaName, elp->relation);
+					elements = lappend(elements, element);
 				}
 				break;
 
@@ -4150,12 +4119,8 @@ transformCreateSchemaStmtElements(List *schemaElts, const char *schemaName)
 				{
 					ViewStmt   *elp = (ViewStmt *) element;
 
-					setSchemaName(cxt.schemaname, &elp->view->schemaname);
-
-					/*
-					 * XXX todo: deal with references between views
-					 */
-					cxt.views = lappend(cxt.views, element);
+					checkSchemaName(pstate, schemaName, elp->view);
+					elements = lappend(elements, element);
 				}
 				break;
 
@@ -4163,8 +4128,8 @@ transformCreateSchemaStmtElements(List *schemaElts, const char *schemaName)
 				{
 					IndexStmt  *elp = (IndexStmt *) element;
 
-					setSchemaName(cxt.schemaname, &elp->relation->schemaname);
-					cxt.indexes = lappend(cxt.indexes, element);
+					checkSchemaName(pstate, schemaName, elp->relation);
+					elements = lappend(elements, element);
 				}
 				break;
 
@@ -4172,13 +4137,13 @@ transformCreateSchemaStmtElements(List *schemaElts, const char *schemaName)
 				{
 					CreateTrigStmt *elp = (CreateTrigStmt *) element;
 
-					setSchemaName(cxt.schemaname, &elp->relation->schemaname);
-					cxt.triggers = lappend(cxt.triggers, element);
+					checkSchemaName(pstate, schemaName, elp->relation);
+					elements = lappend(elements, element);
 				}
 				break;
 
 			case T_GrantStmt:
-				cxt.grants = lappend(cxt.grants, element);
+				elements = lappend(elements, element);
 				break;
 
 			default:
@@ -4187,32 +4152,40 @@ transformCreateSchemaStmtElements(List *schemaElts, const char *schemaName)
 		}
 	}
 
-	result = NIL;
-	result = list_concat(result, cxt.sequences);
-	result = list_concat(result, cxt.tables);
-	result = list_concat(result, cxt.views);
-	result = list_concat(result, cxt.indexes);
-	result = list_concat(result, cxt.triggers);
-	result = list_concat(result, cxt.grants);
-
-	return result;
+	return elements;
 }
 
 /*
- * setSchemaName
- *		Set or check schema name in an element of a CREATE SCHEMA command
+ * checkSchemaName
+ *		Check schema name in an element of a CREATE SCHEMA command
+ *
+ * It's okay if the command doesn't specify a target schema name, because
+ * CreateSchemaCommand will set up the default creation schema to be the
+ * new schema.  But if a target schema name is given, it had better match.
+ * We also have to check that the command doesn't say CREATE TEMP, since
+ * that would likewise put the object into the wrong schema.
  */
 static void
-setSchemaName(const char *context_schema, char **stmt_schema_name)
+checkSchemaName(ParseState *pstate, const char *context_schema,
+				RangeVar *relation)
 {
-	if (*stmt_schema_name == NULL)
-		*stmt_schema_name = unconstify(char *, context_schema);
-	else if (strcmp(context_schema, *stmt_schema_name) != 0)
+	if (relation->schemaname != NULL &&
+		strcmp(context_schema, relation->schemaname) != 0)
 		ereport(ERROR,
-				(errcode(ERRCODE_INVALID_SCHEMA_DEFINITION),
-				 errmsg("CREATE specifies a schema (%s) "
+				errcode(ERRCODE_INVALID_SCHEMA_DEFINITION),
+				errmsg("CREATE specifies a schema (%s) "
 						"different from the one being created (%s)",
-						*stmt_schema_name, context_schema)));
+						relation->schemaname, context_schema),
+				parser_errposition(pstate, relation->location));
+
+	if (relation->relpersistence == RELPERSISTENCE_TEMP)
+	{
+		/* spell this error the same as in RangeVarAdjustRelationPersistence */
+		ereport(ERROR,
+				errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+				errmsg("cannot create temporary relation in non-temporary schema"),
+				parser_errposition(pstate, relation->location));
+	}
 }
 
 /*
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..ec2bbe5587c 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1111,8 +1111,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				 * relation and attribute manipulation
 				 */
 			case T_CreateSchemaStmt:
-				CreateSchemaCommand((CreateSchemaStmt *) parsetree,
-									queryString,
+				CreateSchemaCommand(pstate, (CreateSchemaStmt *) parsetree,
 									pstmt->stmt_location,
 									pstmt->stmt_len);
 
diff --git a/src/include/commands/schemacmds.h b/src/include/commands/schemacmds.h
index 8557176b66a..835265bb67c 100644
--- a/src/include/commands/schemacmds.h
+++ b/src/include/commands/schemacmds.h
@@ -16,12 +16,9 @@
 #define SCHEMACMDS_H
 
 #include "catalog/objectaddress.h"
-#include "nodes/parsenodes.h"
-
-extern Oid	CreateSchemaCommand(CreateSchemaStmt *stmt,
-								const char *queryString,
-								int stmt_location, int stmt_len);
+#include "parser/parse_node.h"
 
+extern Oid	CreateSchemaCommand(ParseState *pstate, CreateSchemaStmt *stmt, int stmt_location, int stmt_len);
 extern ObjectAddress RenameSchema(const char *oldname, const char *newname);
 extern ObjectAddress AlterSchemaOwner(const char *name, Oid newOwnerId);
 extern void AlterSchemaOwner_oid(Oid schemaoid, Oid newOwnerId);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..da514198ced 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -30,7 +30,8 @@ extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-extern List *transformCreateSchemaStmtElements(List *schemaElts,
+extern List *transformCreateSchemaStmtElements(ParseState *pstate,
+											   List *schemaElts,
 											   const char *schemaName);
 extern PartitionBoundSpec *transformPartitionBound(ParseState *pstate, Relation parent,
 												   PartitionBoundSpec *spec);
diff --git a/src/test/regress/expected/create_schema.out b/src/test/regress/expected/create_schema.out
index 93302a07efc..38530c282a9 100644
--- a/src/test/regress/expected/create_schema.out
+++ b/src/test/regress/expected/create_schema.out
@@ -9,55 +9,92 @@ CREATE ROLE regress_create_schema_role SUPERUSER;
 CREATE SCHEMA AUTHORIZATION regress_create_schema_role
   CREATE SEQUENCE schema_not_existing.seq;
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_create_schema_role)
+LINE 2:   CREATE SEQUENCE schema_not_existing.seq;
+                          ^
 CREATE SCHEMA AUTHORIZATION regress_create_schema_role
   CREATE TABLE schema_not_existing.tab (id int);
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_create_schema_role)
+LINE 2:   CREATE TABLE schema_not_existing.tab (id int);
+                       ^
 CREATE SCHEMA AUTHORIZATION regress_create_schema_role
   CREATE VIEW schema_not_existing.view AS SELECT 1;
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_create_schema_role)
+LINE 2:   CREATE VIEW schema_not_existing.view AS SELECT 1;
+                      ^
 CREATE SCHEMA AUTHORIZATION regress_create_schema_role
   CREATE INDEX ON schema_not_existing.tab (id);
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_create_schema_role)
+LINE 2:   CREATE INDEX ON schema_not_existing.tab (id);
+                          ^
 CREATE SCHEMA AUTHORIZATION regress_create_schema_role
   CREATE TRIGGER schema_trig BEFORE INSERT ON schema_not_existing.tab
   EXECUTE FUNCTION schema_trig.no_func();
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_create_schema_role)
+LINE 2:   CREATE TRIGGER schema_trig BEFORE INSERT ON schema_not_exi...
+                                                      ^
 -- Again, with a role specification and no schema names.
 SET ROLE regress_create_schema_role;
 CREATE SCHEMA AUTHORIZATION CURRENT_ROLE
   CREATE SEQUENCE schema_not_existing.seq;
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_create_schema_role)
+LINE 2:   CREATE SEQUENCE schema_not_existing.seq;
+                          ^
 CREATE SCHEMA AUTHORIZATION CURRENT_ROLE
   CREATE TABLE schema_not_existing.tab (id int);
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_create_schema_role)
+LINE 2:   CREATE TABLE schema_not_existing.tab (id int);
+                       ^
 CREATE SCHEMA AUTHORIZATION CURRENT_ROLE
   CREATE VIEW schema_not_existing.view AS SELECT 1;
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_create_schema_role)
+LINE 2:   CREATE VIEW schema_not_existing.view AS SELECT 1;
+                      ^
 CREATE SCHEMA AUTHORIZATION CURRENT_ROLE
   CREATE INDEX ON schema_not_existing.tab (id);
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_create_schema_role)
+LINE 2:   CREATE INDEX ON schema_not_existing.tab (id);
+                          ^
 CREATE SCHEMA AUTHORIZATION CURRENT_ROLE
   CREATE TRIGGER schema_trig BEFORE INSERT ON schema_not_existing.tab
   EXECUTE FUNCTION schema_trig.no_func();
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_create_schema_role)
+LINE 2:   CREATE TRIGGER schema_trig BEFORE INSERT ON schema_not_exi...
+                                                      ^
 -- Again, with a schema name and a role specification.
 CREATE SCHEMA regress_schema_1 AUTHORIZATION CURRENT_ROLE
   CREATE SEQUENCE schema_not_existing.seq;
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_schema_1)
+LINE 2:   CREATE SEQUENCE schema_not_existing.seq;
+                          ^
 CREATE SCHEMA regress_schema_1 AUTHORIZATION CURRENT_ROLE
   CREATE TABLE schema_not_existing.tab (id int);
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_schema_1)
+LINE 2:   CREATE TABLE schema_not_existing.tab (id int);
+                       ^
 CREATE SCHEMA regress_schema_1 AUTHORIZATION CURRENT_ROLE
   CREATE VIEW schema_not_existing.view AS SELECT 1;
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_schema_1)
+LINE 2:   CREATE VIEW schema_not_existing.view AS SELECT 1;
+                      ^
 CREATE SCHEMA regress_schema_1 AUTHORIZATION CURRENT_ROLE
   CREATE INDEX ON schema_not_existing.tab (id);
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_schema_1)
+LINE 2:   CREATE INDEX ON schema_not_existing.tab (id);
+                          ^
 CREATE SCHEMA regress_schema_1 AUTHORIZATION CURRENT_ROLE
   CREATE TRIGGER schema_trig BEFORE INSERT ON schema_not_existing.tab
   EXECUTE FUNCTION schema_trig.no_func();
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_schema_1)
+LINE 2:   CREATE TRIGGER schema_trig BEFORE INSERT ON schema_not_exi...
+                                                      ^
 RESET ROLE;
+--error. Forward reference is not allowed.
+CREATE SCHEMA regress_schema_2
+  CREATE VIEW abcd_view AS SELECT a FROM abcd
+  CREATE TABLE abcd (a int);
+ERROR:  relation "abcd" does not exist
+LINE 2:   CREATE VIEW abcd_view AS SELECT a FROM abcd
+                                                 ^
 -- Cases where the schema creation succeeds.
 -- The schema created matches the role name.
 CREATE SCHEMA AUTHORIZATION regress_create_schema_role
diff --git a/src/test/regress/expected/create_view.out b/src/test/regress/expected/create_view.out
index f551624afb3..b305ceea033 100644
--- a/src/test/regress/expected/create_view.out
+++ b/src/test/regress/expected/create_view.out
@@ -126,6 +126,8 @@ ERROR:  cannot create temporary relation in non-temporary schema
 CREATE SCHEMA test_view_schema
     CREATE TEMP VIEW testview AS SELECT 1;
 ERROR:  cannot create temporary relation in non-temporary schema
+LINE 2:     CREATE TEMP VIEW testview AS SELECT 1;
+                             ^
 -- joins: if any of the join relations are temporary, the view
 -- should also be temporary
 -- should be non-temp
diff --git a/src/test/regress/expected/event_trigger.out b/src/test/regress/expected/event_trigger.out
index 7b2198eac6f..bafde8706fe 100644
--- a/src/test/regress/expected/event_trigger.out
+++ b/src/test/regress/expected/event_trigger.out
@@ -411,12 +411,12 @@ NOTICE:  END: command_tag=CREATE TABLE type=table identity=evttrig.one
 NOTICE:  END: command_tag=CREATE INDEX type=index identity=evttrig.one_pkey
 NOTICE:  END: command_tag=ALTER SEQUENCE type=sequence identity=evttrig.one_col_a_seq
 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 INDEX type=index identity=evttrig.one_idx
 -- Partitioned tables with a partitioned index
 CREATE TABLE evttrig.parted (
     id int PRIMARY KEY)
diff --git a/src/test/regress/expected/namespace.out b/src/test/regress/expected/namespace.out
index dbbda72d395..2e582e783c2 100644
--- a/src/test/regress/expected/namespace.out
+++ b/src/test/regress/expected/namespace.out
@@ -10,13 +10,14 @@ SELECT pg_catalog.set_config('search_path', ' ', false);
 (1 row)
 
 CREATE SCHEMA test_ns_schema_1
+       CREATE TABLE abc (
+              a serial,
+              b int UNIQUE
+       )
        CREATE UNIQUE INDEX abc_a_idx ON abc (a)
        CREATE VIEW abc_view AS
               SELECT a+1 AS a, b+1 AS b FROM abc
-       CREATE TABLE abc (
-              a serial,
-              b int UNIQUE
-       );
+;
 -- verify that the correct search_path restored on abort
 SET search_path to public;
 BEGIN;
diff --git a/src/test/regress/sql/create_schema.sql b/src/test/regress/sql/create_schema.sql
index 1b7064247a1..b3dc1cfd758 100644
--- a/src/test/regress/sql/create_schema.sql
+++ b/src/test/regress/sql/create_schema.sql
@@ -47,6 +47,11 @@ CREATE SCHEMA regress_schema_1 AUTHORIZATION CURRENT_ROLE
   EXECUTE FUNCTION schema_trig.no_func();
 RESET ROLE;
 
+--error. Forward reference is not allowed.
+CREATE SCHEMA regress_schema_2
+  CREATE VIEW abcd_view AS SELECT a FROM abcd
+  CREATE TABLE abcd (a int);
+
 -- Cases where the schema creation succeeds.
 -- The schema created matches the role name.
 CREATE SCHEMA AUTHORIZATION regress_create_schema_role
diff --git a/src/test/regress/sql/namespace.sql b/src/test/regress/sql/namespace.sql
index 306cdc2d8c6..a75d4f580d3 100644
--- a/src/test/regress/sql/namespace.sql
+++ b/src/test/regress/sql/namespace.sql
@@ -7,15 +7,16 @@
 SELECT pg_catalog.set_config('search_path', ' ', false);
 
 CREATE SCHEMA test_ns_schema_1
+       CREATE TABLE abc (
+              a serial,
+              b int UNIQUE
+       )
+
        CREATE UNIQUE INDEX abc_a_idx ON abc (a)
 
        CREATE VIEW abc_view AS
               SELECT a+1 AS a, b+1 AS b FROM abc
-
-       CREATE TABLE abc (
-              a serial,
-              b int UNIQUE
-       );
+;
 
 -- verify that the correct search_path restored on abort
 SET search_path to public;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index e6f2e93b2d6..6a20bfa4b4d 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -558,7 +558,6 @@ CreateRangeStmt
 CreateReplicationSlotCmd
 CreateRoleStmt
 CreateSchemaStmt
-CreateSchemaStmtContext
 CreateSeqStmt
 CreateStatsStmt
 CreateStmt
-- 
2.34.1



  [text/x-patch] v6-0003-CREATE-SCHEMA-CREATE-COLLATION.patch (12.4K, 4-v6-0003-CREATE-SCHEMA-CREATE-COLLATION.patch)
  download | inline diff:
From 9ff37e0923317cbce1722ea4c0260e72ce0db9c2 Mon Sep 17 00:00:00 2001
From: jian he <[email protected]>
Date: Tue, 19 Aug 2025 11:31:08 +0800
Subject: [PATCH v6 3/3] CREATE SCHEMA CREATE COLLATION

SQL standard allow collation to be specified with CREATE SCHEMA
statement. This patch adds support in PostgreSQL for that.

For example:
    CREATE SCHEMA schema_name AUTHORIZATION CURRENT_ROLE
    CREATE COLLATION coll_icu_und FROM "und-x-icu";

The collation will be created within the to be created schema.
The collation name can be schema-qualified or database-qualified,
however it's not allowed to let collation create within a different schema.

Note: src/bin/psql/tab-complete.in.c changes seems incorrect.

Discussion: https://postgr.es/m/CALdSSPh4jUSDsWu3K58hjO60wnTRR0DuO4CKRcwa8EVuOSfXxg@mail.gmail.com
---
 src/backend/catalog/objectaddress.c           | 16 ++++++
 src/backend/parser/gram.y                     |  1 +
 src/backend/parser/parse_utilcmd.c            | 23 +++++++++
 src/bin/psql/tab-complete.in.c                |  8 +--
 src/include/catalog/objectaddress.h           |  1 +
 .../expected/create_schema.out                |  4 +-
 .../test_ddl_deparse/sql/create_schema.sql    |  3 +-
 src/test/regress/expected/create_schema.out   | 50 +++++++++++++++++++
 src/test/regress/sql/create_schema.sql        | 32 ++++++++++++
 9 files changed, 132 insertions(+), 6 deletions(-)

diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 0102c9984e7..55b65dd08a3 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -2622,6 +2622,22 @@ read_objtype_from_string(const char *objtype)
 	return -1;					/* keep compiler quiet */
 }
 
+const char *
+stringify_objtype(ObjectType objtype)
+{
+	int			i;
+
+	for (i = 0; i < lengthof(ObjectTypeMap); i++)
+	{
+		if (ObjectTypeMap[i].tm_type == objtype)
+			return ObjectTypeMap[i].tm_name;
+	}
+	ereport(ERROR,
+			errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+			errmsg("unrecognized object type %d", objtype));
+
+	return NULL;					/* keep compiler quiet */
+}
 /*
  * Interfaces to reference fields of ObjectPropertyType
  */
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 3aca508b08f..30c4a567502 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -1627,6 +1627,7 @@ schema_stmt:
 			| GrantStmt
 			| ViewStmt
 			| CreateDomainStmt
+			| DefineStmt
 		;
 
 
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 218ec6f0982..5fd6fc63ed6 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -4171,6 +4171,29 @@ transformCreateSchemaStmtElements(ParseState *pstate, List *schemaElts,
 				}
 				break;
 
+			case T_DefineStmt:
+				{
+					char	   *coll_schema = NULL;
+					char	   *collName;
+					DefineStmt *stmt = (DefineStmt *) element;
+
+					if (stmt->kind != OBJECT_COLLATION)
+						ereport(ERROR,
+								errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								errmsg("CREATE SCHEMA ... CREATE OBJECT currently not support for %s",
+									   stringify_objtype(stmt->kind)));
+
+					DeconstructQualifiedName(stmt->defnames, &coll_schema, &collName);
+					if (coll_schema && strcmp(coll_schema, schemaName) != 0)
+						ereport(ERROR,
+								errcode(ERRCODE_INVALID_SCHEMA_DEFINITION),
+								errmsg("CREATE specifies a schema (%s) "
+									   "different from the one being created (%s)",
+									   schemaName, coll_schema));
+
+					elements = lappend(elements, element);
+				}
+				break;
 			default:
 				elog(ERROR, "unrecognized node type: %d",
 					 (int) nodeTag(element));
diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c
index d7a8c769e35..b6beb868eb2 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -2178,7 +2178,7 @@ match_previous_words(int pattern_id,
 	{
 		/* only some object types can be created as part of CREATE SCHEMA */
 		if (HeadMatches("CREATE", "SCHEMA"))
-			COMPLETE_WITH("TABLE", "VIEW", "INDEX", "SEQUENCE", "TRIGGER", "DOMAIN",
+			COMPLETE_WITH("TABLE", "VIEW", "INDEX", "SEQUENCE", "TRIGGER", "DOMAIN", "COLLATION",
 			/* for INDEX and TABLE/SEQUENCE, respectively */
 						  "UNIQUE", "UNLOGGED");
 		else
@@ -3370,10 +3370,10 @@ match_previous_words(int pattern_id,
 	else if (Matches("CREATE", "ACCESS", "METHOD", MatchAny, "TYPE", MatchAny))
 		COMPLETE_WITH("HANDLER");
 
-	/* CREATE COLLATION */
-	else if (Matches("CREATE", "COLLATION", MatchAny))
+	/* CREATE COLLATION --- is allowed inside CREATE SCHEMA, so use TailMatches */
+	else if (TailMatches("CREATE", "COLLATION", MatchAny))
 		COMPLETE_WITH("(", "FROM");
-	else if (Matches("CREATE", "COLLATION", MatchAny, "FROM"))
+	else if (TailMatches("CREATE", "COLLATION", MatchAny, "FROM"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_collations);
 	else if (HeadMatches("CREATE", "COLLATION", MatchAny, "(*"))
 	{
diff --git a/src/include/catalog/objectaddress.h b/src/include/catalog/objectaddress.h
index 630434b73cf..0070e77731e 100644
--- a/src/include/catalog/objectaddress.h
+++ b/src/include/catalog/objectaddress.h
@@ -79,6 +79,7 @@ extern char *getObjectDescription(const ObjectAddress *object,
 extern char *getObjectDescriptionOids(Oid classid, Oid objid);
 
 extern int	read_objtype_from_string(const char *objtype);
+const char *stringify_objtype(ObjectType objtype);
 extern char *getObjectTypeDescription(const ObjectAddress *object,
 									  bool missing_ok);
 extern char *getObjectIdentity(const ObjectAddress *object,
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 d73c4702051..19e0aad3cb0 100644
--- a/src/test/modules/test_ddl_deparse/expected/create_schema.out
+++ b/src/test/modules/test_ddl_deparse/expected/create_schema.out
@@ -14,8 +14,10 @@ NOTICE:  schema "baz" already exists, skipping
 CREATE SCHEMA element_test
   CREATE TABLE foo (id int)
   CREATE VIEW bar AS SELECT * FROM foo
-  CREATE DOMAIN d1 AS INT;
+  CREATE DOMAIN d1 AS INT
+  CREATE COLLATION coll_icu_und FROM "und-x-icu";
 NOTICE:  DDL test: type simple, tag CREATE SCHEMA
 NOTICE:  DDL test: type simple, tag CREATE TABLE
 NOTICE:  DDL test: type simple, tag CREATE VIEW
 NOTICE:  DDL test: type simple, tag CREATE DOMAIN
+NOTICE:  DDL test: type simple, tag CREATE COLLATION
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 57ada462070..c9a70a0862a 100644
--- a/src/test/modules/test_ddl_deparse/sql/create_schema.sql
+++ b/src/test/modules/test_ddl_deparse/sql/create_schema.sql
@@ -15,4 +15,5 @@ CREATE SCHEMA IF NOT EXISTS baz;
 CREATE SCHEMA element_test
   CREATE TABLE foo (id int)
   CREATE VIEW bar AS SELECT * FROM foo
-  CREATE DOMAIN d1 AS INT;
+  CREATE DOMAIN d1 AS INT
+  CREATE COLLATION coll_icu_und FROM "und-x-icu";
diff --git a/src/test/regress/expected/create_schema.out b/src/test/regress/expected/create_schema.out
index d6718a9f519..2669da3b15a 100644
--- a/src/test/regress/expected/create_schema.out
+++ b/src/test/regress/expected/create_schema.out
@@ -177,6 +177,48 @@ CREATE SCHEMA regress_schema_3 AUTHORIZATION CURRENT_ROLE
  regress_schema_3 | ss1  | regress_schema_3.ss |           |          | 'hello'::text | 
 (2 rows)
 
+-- Cases where the schema creation with collations
+--fail. cannot CREATE DOMAIN to other schema
+CREATE SCHEMA regress_schema_4 AUTHORIZATION CURRENT_ROLE
+  CREATE COLLATION public.coll_icu_und FROM "und-x-icu";
+ERROR:  CREATE specifies a schema (regress_schema_4) different from the one being created (public)
+--fail. improper qualified name
+CREATE SCHEMA regress_schema_4 AUTHORIZATION CURRENT_ROLE
+  CREATE COLLATION postgres.public.coll_icu_und FROM "und-x-icu";
+ERROR:  cross-database references are not implemented: postgres.public.coll_icu_und
+--fail. only support collation objbect for DefineStmt node
+CREATE SCHEMA regress_schema_4 AUTHORIZATION CURRENT_ROLE
+  CREATE AGGREGATE balk(int4)(SFUNC = int4_sum(int8, int4),STYPE = int8, PARALLEL = SAFE, INITCOND = '0');
+ERROR:  CREATE SCHEMA ... CREATE OBJECT currently not support for aggregate
+--fail. Execute subcommands in order; we do not implicitly reorder them.
+CREATE SCHEMA regress_schema_4 AUTHORIZATION CURRENT_ROLE
+  CREATE TABLE tts(a TEXT COLLATE coll_icu_und)
+  CREATE COLLATION coll_icu_und FROM "und-x-icu";
+ERROR:  collation "coll_icu_und" for encoding "UTF8" does not exist
+LINE 2:   CREATE TABLE tts(a TEXT COLLATE coll_icu_und)
+                                  ^
+--ok, qualified schema name for domain should be same as the created schema
+CREATE SCHEMA regress_schema_4 AUTHORIZATION CURRENT_ROLE
+  CREATE COLLATION regress_schema_4.coll_icu_und FROM "und-x-icu"
+  CREATE TABLE t(a TEXT COLLATE regress_schema_4.coll_icu_und);
+\dO regress_schema_4.*
+                                         List of collations
+      Schema      |     Name     | Provider | Collate | Ctype | Locale | ICU Rules | Deterministic? 
+------------------+--------------+----------+---------+-------+--------+-----------+----------------
+ regress_schema_4 | coll_icu_und | icu      |         |       | und    |           | yes
+(1 row)
+
+--ok, no qualified schema name for collation
+CREATE SCHEMA regress_schema_5 AUTHORIZATION CURRENT_ROLE
+  CREATE COLLATION coll_icu_und FROM "und-x-icu"
+  CREATE TABLE t(a TEXT COLLATE regress_schema_5.coll_icu_und);
+\dO regress_schema_5.*
+                                         List of collations
+      Schema      |     Name     | Provider | Collate | Ctype | Locale | ICU Rules | Deterministic? 
+------------------+--------------+----------+---------+-------+--------+-----------+----------------
+ regress_schema_5 | coll_icu_und | icu      |         |       | und    |           | yes
+(1 row)
+
 DROP SCHEMA regress_schema_2 CASCADE;
 NOTICE:  drop cascades to 2 other objects
 DETAIL:  drop cascades to type regress_schema_2.ss
@@ -187,5 +229,13 @@ DETAIL:  drop cascades to type regress_schema_3.ss
 drop cascades to type regress_schema_3.ss1
 drop cascades to view regress_schema_3.test
 drop cascades to table regress_schema_3.t
+DROP SCHEMA regress_schema_4 CASCADE;
+NOTICE:  drop cascades to 2 other objects
+DETAIL:  drop cascades to collation regress_schema_4.coll_icu_und
+drop cascades to table regress_schema_4.t
+DROP SCHEMA regress_schema_5 CASCADE;
+NOTICE:  drop cascades to 2 other objects
+DETAIL:  drop cascades to collation regress_schema_5.coll_icu_und
+drop cascades to table regress_schema_5.t
 -- Clean up
 DROP ROLE regress_create_schema_role;
diff --git a/src/test/regress/sql/create_schema.sql b/src/test/regress/sql/create_schema.sql
index 3028148e96b..e99915da383 100644
--- a/src/test/regress/sql/create_schema.sql
+++ b/src/test/regress/sql/create_schema.sql
@@ -103,8 +103,40 @@ CREATE SCHEMA regress_schema_3 AUTHORIZATION CURRENT_ROLE
   CREATE TABLE t(a ss1);
 \dD regress_schema_3.*
 
+-- Cases where the schema creation with collations
+--fail. cannot CREATE DOMAIN to other schema
+CREATE SCHEMA regress_schema_4 AUTHORIZATION CURRENT_ROLE
+  CREATE COLLATION public.coll_icu_und FROM "und-x-icu";
+
+--fail. improper qualified name
+CREATE SCHEMA regress_schema_4 AUTHORIZATION CURRENT_ROLE
+  CREATE COLLATION postgres.public.coll_icu_und FROM "und-x-icu";
+
+--fail. only support collation objbect for DefineStmt node
+CREATE SCHEMA regress_schema_4 AUTHORIZATION CURRENT_ROLE
+  CREATE AGGREGATE balk(int4)(SFUNC = int4_sum(int8, int4),STYPE = int8, PARALLEL = SAFE, INITCOND = '0');
+
+--fail. Execute subcommands in order; we do not implicitly reorder them.
+CREATE SCHEMA regress_schema_4 AUTHORIZATION CURRENT_ROLE
+  CREATE TABLE tts(a TEXT COLLATE coll_icu_und)
+  CREATE COLLATION coll_icu_und FROM "und-x-icu";
+
+--ok, qualified schema name for domain should be same as the created schema
+CREATE SCHEMA regress_schema_4 AUTHORIZATION CURRENT_ROLE
+  CREATE COLLATION regress_schema_4.coll_icu_und FROM "und-x-icu"
+  CREATE TABLE t(a TEXT COLLATE regress_schema_4.coll_icu_und);
+\dO regress_schema_4.*
+
+--ok, no qualified schema name for collation
+CREATE SCHEMA regress_schema_5 AUTHORIZATION CURRENT_ROLE
+  CREATE COLLATION coll_icu_und FROM "und-x-icu"
+  CREATE TABLE t(a TEXT COLLATE regress_schema_5.coll_icu_und);
+\dO regress_schema_5.*
+
 DROP SCHEMA regress_schema_2 CASCADE;
 DROP SCHEMA regress_schema_3 CASCADE;
+DROP SCHEMA regress_schema_4 CASCADE;
+DROP SCHEMA regress_schema_5 CASCADE;
 
 -- Clean up
 DROP ROLE regress_create_schema_role;
-- 
2.34.1



^ permalink  raw  reply  [nested|flat] 32+ messages in thread

* Re: CREATE SCHEMA ... CREATE DOMAIN support
  2024-11-30 19:08 Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  2024-12-03 19:35 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Peter Eisentraut <[email protected]>
  2024-12-03 20:07   ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  2024-12-11 06:22     ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <[email protected]>
  2024-12-11 17:08       ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  2025-08-19 03:36         ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <[email protected]>
@ 2025-08-22 08:59           ` Kirill Reshke <[email protected]>
  2025-08-26 04:53             ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <[email protected]>
  2025-09-02 21:24             ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  0 siblings, 2 replies; 32+ messages in thread

From: Kirill Reshke @ 2025-08-22 08:59 UTC (permalink / raw)
  To: jian he <[email protected]>; +Cc: Tom Lane <[email protected]>; Peter Eisentraut <[email protected]>; PostgreSQL Hackers <[email protected]>

On Tue, 19 Aug 2025 at 08:37, jian he <[email protected]> wrote:
>
> On Thu, Dec 12, 2024 at 1:08 AM Tom Lane <[email protected]> wrote:
> > This cannot possibly work if an object-type-based re-ordering
> > is done to it.
> >
> > So IMV, we have three possibilities:
> >
> > 1. CREATE SCHEMA's schema-element feature remains forevermore
> > a sad joke that (a) doesn't cover nearly enough to be useful and
> > (b) doesn't come close to doing what the spec says it should.
> >
> > 2. We invest an enormous amount of engineering effort on trying
> > to extract dependencies from not-yet-analyzed parse trees, after
> > which we invest a bunch more effort figuring out heuristics for
> > ordering the subcommands in the face of circular dependencies.
> > (Some of that could be stolen from pg_dump, but not all: pg_dump
> > only has to resolve a limited set of cases.)
> >
> > 3. We bypass the need for #2 by decreeing that we'll execute
> > the subcommands in order.
> >
> >
> > >> PS: if we were really excited about allowing circular FKs to be
> > >> made within CREATE SCHEMA, a possible though non-standard answer
> > >> would be to allow ALTER TABLE ADD CONSTRAINT as a <schema element>.
> >
> > > That's a nice feature to have by itself?
> >
> > Not unless we abandon the idea of subcommand reordering, because
> > where are you going to put the ALTER TABLE subcommands?
> >
>
> hi.

Hi!

> move this forward with option #3 (executing the subcommands in order).

Thank you. I am +1 on option #3.

> pg_dump don't use CREATE SCHEMA ...CREATE ...
> so if we error out
> CREATE SCHEMA regress_schema_2 CREATE VIEW abcd_view AS SELECT a FROM
> abcd CREATE TABLE abcd (a int);
> it won't be a big compatibility issue?
> Also this thread doesn’t show strong support for sorting the subcommands.
>
> the full <schema definition> in 11.1 is:
> 11.1 <schema definition>
>
> <schema element> ::=
> <table definition>
> | <view definition>
> | <domain definition>
> | <character set definition>
> | <collation definition>
> | <transliteration definition>
> | <assertion definition>
> | <trigger definition>
> | <user-defined type definition>
> | <user-defined cast definition>
> | <user-defined ordering definition>
> | <transform definition>
> | <schema routine>
> | <sequence generator definition>
> | <grant statement>
> | <role definition>
>
> so I also add support for CREATE SCHEMA CREATE COLLATION.
>
> v6-0001-Don-t-try-to-re-order-the-subcommands-of-CREATE-SCHEMA.patch
> v6-0002-CREATE-SCHEMA-CREATE-DOMAIN.patch
> v6-0003-CREATE-SCHEMA-CREATE-COLLATION.patch
>
> v6-0001-Don-t-try-to-re-order-the-subcommands-of-CREATE-SCHEMA.patch
> is refactor/rebase based on
> v1-0001-Don-t-try-to-re-order-the-subcommands-of-CREATE-S.patch
>
> v6-0002-CREATE-SCHEMA-CREATE-DOMAIN.patch
> for CREATE SCHEMA ... CREATE-DOMAIN
>
> v6-0003-CREATE-SCHEMA-CREATE-COLLATION.patch
> for CREATE SCHEMA ... CREATE-COLLATION


With these patches applied:
```
reshke=# create schema  sh1 create type tp as  (i text);
ERROR:  unrecognized node type: 226
```
Without patches it will be a syntax error.



Also we need a better error message in this:
"CREATE SCHEMA ... CREATE OBJECT currently not support for..."

First of all, is it s/support/supported/ ? Also would vote for
something like "%s is not yet supported inside schema definition."
WDYT?


-- 
Best regards,
Kirill Reshke





^ permalink  raw  reply  [nested|flat] 32+ messages in thread

* Re: CREATE SCHEMA ... CREATE DOMAIN support
  2024-11-30 19:08 Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  2024-12-03 19:35 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Peter Eisentraut <[email protected]>
  2024-12-03 20:07   ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  2024-12-11 06:22     ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <[email protected]>
  2024-12-11 17:08       ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  2025-08-19 03:36         ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <[email protected]>
  2025-08-22 08:59           ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <[email protected]>
@ 2025-08-26 04:53             ` jian he <[email protected]>
  1 sibling, 0 replies; 32+ messages in thread

From: jian he @ 2025-08-26 04:53 UTC (permalink / raw)
  To: Kirill Reshke <[email protected]>; +Cc: Tom Lane <[email protected]>; Peter Eisentraut <[email protected]>; PostgreSQL Hackers <[email protected]>

On Fri, Aug 22, 2025 at 4:59 PM Kirill Reshke <[email protected]> wrote:
>
> >
> > the full <schema definition> in 11.1 is:
> > 11.1 <schema definition>
> >
> > <schema element> ::=
> > <table definition>
> > | <view definition>
> > | <domain definition>
> > | <character set definition>
> > | <collation definition>
> > | <transliteration definition>
> > | <assertion definition>
> > | <trigger definition>
> > | <user-defined type definition>
> > | <user-defined cast definition>
> > | <user-defined ordering definition>
> > | <transform definition>
> > | <schema routine>
> > | <sequence generator definition>
> > | <grant statement>
> > | <role definition>
> >
>

I also added CREATE SCHEMA CREATE TYPE.

> With these patches applied:
> ```
> reshke=# create schema  sh1 create type tp as  (i text);
> ERROR:  unrecognized node type: 226
> ```
> Without patches it will be a syntax error.
>

This issue is solved in V7.

>
> Also we need a better error message in this:
> "CREATE SCHEMA ... CREATE OBJECT currently not support for..."
>
> First of all, is it s/support/supported/ ? Also would vote for
> something like "%s is not yet supported inside schema definition."
> WDYT?
>

"%s is not yet supported inside schema definition." is good option,
but how about

ereport(ERROR,
    errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
    errmsg("CREATE SCHEMA ... CREATE %s currently not supported",
                asc_toupper(stringify_objtype(stmt->kind),
                strlen(stringify_objtype(stmt->kind)))))

stringify_objtype will produce the object type name, then capitalize it.
IMHO, now the error message would be more explicit.
+--fail. only support collation object for DefineStmt node
+CREATE SCHEMA regress_schema_4 AUTHORIZATION CURRENT_ROLE
+  CREATE AGGREGATE balk(int4)(SFUNC = int4_sum(int8, int4),STYPE =
int8, PARALLEL = SAFE, INITCOND = '0');
+ERROR:  CREATE SCHEMA ... CREATE AGGREGATE currently not supported


not all CI test machine encoding is UTF8, CREATE COLLATION depends on encoding,
if fail, error message may different on different machines, So we have to put
some of the tests to collate.icu.utf8.sql.

Please check the latest attached.
v7-0001-Don-t-try-to-re-order-the-subcommands-of-CREATE-SCHEMA.patch
v7-0002-CREATE-SCHEMA-CREATE-DOMAIN.patch
v7-0003-CREATE-SCHEMA-CREATE-COLLATION.patch
v7-0004-CREATE-SCHEMA-CREATE-TYPE.patch


Attachments:

  [text/x-patch] v7-0002-CREATE-SCHEMA-CREATE-DOMAIN.patch (12.3K, 2-v7-0002-CREATE-SCHEMA-CREATE-DOMAIN.patch)
  download | inline diff:
From 234796bc14a3ae8413833718a86aeffbc110ae48 Mon Sep 17 00:00:00 2001
From: jian he <[email protected]>
Date: Mon, 18 Aug 2025 16:28:37 +0800
Subject: [PATCH v7 2/4] CREATE SCHEMA CREATE DOMAIN

SQL standard allow domain to be specified with CREATE SCHEMA
statement. This patch adds support in PostgreSQL for that.

For example:
    CREATE SCHEMA schema_name AUTHORIZATION CURRENT_ROLE
    create domain ss as text not null;

The domain will be created within the to be created schema.
The domain name can be schema-qualified or database-qualified,
however it's not allowed to let domain create within a different schema.

Author: Kirill Reshke <[email protected]>
Author: Jian He <[email protected]>
Reviewed-by: Alvaro Herrera <[email protected]>
Reviewed-by: Tom Lane <[email protected]>

Discussion: https://postgr.es/m/CALdSSPh4jUSDsWu3K58hjO60wnTRR0DuO4CKRcwa8EVuOSfXxg@mail.gmail.com
---
 doc/src/sgml/ref/create_schema.sgml           |  4 +-
 src/backend/parser/gram.y                     |  1 +
 src/backend/parser/parse_utilcmd.c            | 25 +++++++++
 src/bin/psql/tab-complete.in.c                | 12 ++--
 .../expected/create_schema.out                |  4 +-
 .../test_ddl_deparse/sql/create_schema.sql    |  3 +-
 src/test/regress/expected/create_schema.out   | 56 +++++++++++++++++++
 src/test/regress/sql/create_schema.sql        | 35 ++++++++++++
 8 files changed, 130 insertions(+), 10 deletions(-)

diff --git a/doc/src/sgml/ref/create_schema.sgml b/doc/src/sgml/ref/create_schema.sgml
index 625793a6b67..79186d2b936 100644
--- a/doc/src/sgml/ref/create_schema.sgml
+++ b/doc/src/sgml/ref/create_schema.sgml
@@ -100,8 +100,8 @@ CREATE SCHEMA IF NOT EXISTS AUTHORIZATION <replaceable class="parameter">role_sp
       <listitem>
        <para>
         An SQL statement defining an object to be created within the
-        schema. Currently, only <command>CREATE
-        TABLE</command>, <command>CREATE VIEW</command>, <command>CREATE
+        schema. Currently, only <command>CREATE DOMAIN</command>
+        <command>CREATE TABLE</command>, <command>CREATE VIEW</command>, <command>CREATE
         INDEX</command>, <command>CREATE SEQUENCE</command>, <command>CREATE
         TRIGGER</command> and <command>GRANT</command> are accepted as clauses
         within <command>CREATE SCHEMA</command>. Other kinds of objects may
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index db43034b9db..3aca508b08f 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -1626,6 +1626,7 @@ schema_stmt:
 			| CreateTrigStmt
 			| GrantStmt
 			| ViewStmt
+			| CreateDomainStmt
 		;
 
 
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 74672a458d5..218ec6f0982 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -4146,6 +4146,31 @@ transformCreateSchemaStmtElements(ParseState *pstate, List *schemaElts,
 				elements = lappend(elements, element);
 				break;
 
+			case T_CreateDomainStmt:
+				{
+					char	   *domain_schema = NULL;
+					CreateDomainStmt *elp = (CreateDomainStmt *) element;
+
+					/*
+					 * The schema of the DOMAIN must match the schema being created.
+					 * If the domain name length exceeds 3, it will fail in
+					 * DeconstructQualifiedName.
+					*/
+					if (list_length(elp->domainname) == 2)
+						domain_schema = strVal(list_nth(elp->domainname, 0));
+					else if (list_length(elp->domainname) == 3)
+						domain_schema = strVal(list_nth(elp->domainname, 1));
+
+					if (domain_schema != NULL && strcmp(domain_schema, schemaName) != 0)
+						ereport(ERROR,
+								errcode(ERRCODE_INVALID_SCHEMA_DEFINITION),
+								errmsg("CREATE specifies a schema (%s) "
+									   "different from the one being created (%s)",
+									   schemaName, domain_schema));
+					elements = lappend(elements, element);
+				}
+				break;
+
 			default:
 				elog(ERROR, "unrecognized node type: %d",
 					 (int) nodeTag(element));
diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c
index 8b10f2313f3..d7a8c769e35 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -2178,7 +2178,7 @@ match_previous_words(int pattern_id,
 	{
 		/* only some object types can be created as part of CREATE SCHEMA */
 		if (HeadMatches("CREATE", "SCHEMA"))
-			COMPLETE_WITH("TABLE", "VIEW", "INDEX", "SEQUENCE", "TRIGGER",
+			COMPLETE_WITH("TABLE", "VIEW", "INDEX", "SEQUENCE", "TRIGGER", "DOMAIN",
 			/* for INDEX and TABLE/SEQUENCE, respectively */
 						  "UNIQUE", "UNLOGGED");
 		else
@@ -3399,15 +3399,15 @@ match_previous_words(int pattern_id,
 	else if (Matches("CREATE", "DATABASE", MatchAny, "STRATEGY"))
 		COMPLETE_WITH("WAL_LOG", "FILE_COPY");
 
-	/* CREATE DOMAIN */
-	else if (Matches("CREATE", "DOMAIN", MatchAny))
+	/* CREATE DOMAIN --- is allowed inside CREATE SCHEMA, so use TailMatches */
+	else if (TailMatches("CREATE", "DOMAIN", MatchAny))
 		COMPLETE_WITH("AS");
-	else if (Matches("CREATE", "DOMAIN", MatchAny, "AS"))
+	else if (TailMatches("CREATE", "DOMAIN", MatchAny, "AS"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes);
-	else if (Matches("CREATE", "DOMAIN", MatchAny, "AS", MatchAny))
+	else if (TailMatches("CREATE", "DOMAIN", MatchAny, "AS", MatchAny))
 		COMPLETE_WITH("COLLATE", "DEFAULT", "CONSTRAINT",
 					  "NOT NULL", "NULL", "CHECK (");
-	else if (Matches("CREATE", "DOMAIN", MatchAny, "COLLATE"))
+	else if (TailMatches("CREATE", "DOMAIN", MatchAny, "COLLATE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_collations);
 
 	/* CREATE EXTENSION */
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..d73c4702051 100644
--- a/src/test/modules/test_ddl_deparse/expected/create_schema.out
+++ b/src/test/modules/test_ddl_deparse/expected/create_schema.out
@@ -13,7 +13,9 @@ CREATE SCHEMA IF NOT EXISTS baz;
 NOTICE:  schema "baz" already exists, skipping
 CREATE SCHEMA element_test
   CREATE TABLE foo (id int)
-  CREATE VIEW bar AS SELECT * FROM foo;
+  CREATE VIEW bar AS SELECT * FROM foo
+  CREATE DOMAIN d1 AS INT;
 NOTICE:  DDL test: type simple, tag CREATE SCHEMA
 NOTICE:  DDL test: type simple, tag CREATE TABLE
 NOTICE:  DDL test: type simple, tag CREATE VIEW
+NOTICE:  DDL test: type simple, tag CREATE DOMAIN
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..57ada462070 100644
--- a/src/test/modules/test_ddl_deparse/sql/create_schema.sql
+++ b/src/test/modules/test_ddl_deparse/sql/create_schema.sql
@@ -14,4 +14,5 @@ CREATE SCHEMA IF NOT EXISTS baz;
 
 CREATE SCHEMA element_test
   CREATE TABLE foo (id int)
-  CREATE VIEW bar AS SELECT * FROM foo;
+  CREATE VIEW bar AS SELECT * FROM foo
+  CREATE DOMAIN d1 AS INT;
diff --git a/src/test/regress/expected/create_schema.out b/src/test/regress/expected/create_schema.out
index 38530c282a9..d6718a9f519 100644
--- a/src/test/regress/expected/create_schema.out
+++ b/src/test/regress/expected/create_schema.out
@@ -131,5 +131,61 @@ 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;
+-- Cases where the schema creation with domain.
+--fail. cannot CREATE DOMAIN to other schema
+CREATE SCHEMA regress_schema_2 AUTHORIZATION CURRENT_ROLE
+  CREATE DOMAIN public.ss AS TEXT NOT NULL DEFAULT 'hello' CONSTRAINT nn CHECK (VALUE <> 'hello')
+  CREATE TABLE t(a ss);
+ERROR:  CREATE specifies a schema (regress_schema_2) different from the one being created (public)
+--fail. cannot CREATE DOMAIN to other schema
+CREATE SCHEMA regress_schema_2 AUTHORIZATION CURRENT_ROLE
+  CREATE DOMAIN postgres.public.ss AS TEXT NOT NULL CONSTRAINT nn CHECK (VALUE <> 'hello') DEFAULT 'hello'
+  CREATE TABLE t(a ss);
+ERROR:  CREATE specifies a schema (regress_schema_2) different from the one being created (public)
+--fail. improper qualified name
+CREATE SCHEMA regress_schema_2 CREATE DOMAIN ss.postgres.regress_schema_2.ss AS TEXT;
+ERROR:  improper qualified name (too many dotted names): ss.postgres.regress_schema_2.ss
+--fail. Execute subcommands in order; we do not implicitly reorder them.
+CREATE SCHEMA regress_schema_2 AUTHORIZATION CURRENT_ROLE
+  CREATE DOMAIN ss1 AS ss
+  CREATE DOMAIN ss AS TEXT;
+ERROR:  type "ss" does not exist
+LINE 2:   CREATE DOMAIN ss1 AS ss
+                               ^
+--ok, qualified schema name for domain should be same as the created schema.
+CREATE SCHEMA regress_schema_2 AUTHORIZATION CURRENT_ROLE
+  CREATE DOMAIN regress_schema_2.ss AS TEXT NOT NULL CONSTRAINT nn CHECK (VALUE <> 'hello') DEFAULT 'hello' COLLATE "C"
+  CREATE TABLE t(a regress_schema_2.ss);
+\dD regress_schema_2.*
+                                            List of domains
+      Schema      | Name | Type | Collation | Nullable |    Default    |             Check              
+------------------+------+------+-----------+----------+---------------+--------------------------------
+ regress_schema_2 | ss   | text | C         | not null | 'hello'::text | CHECK (VALUE <> 'hello'::text)
+(1 row)
+
+--ok, no qualified schema name for domain.
+CREATE SCHEMA regress_schema_3 AUTHORIZATION CURRENT_ROLE
+  CREATE DOMAIN ss AS TEXT  CONSTRAINT nn CHECK (VALUE <> 'hello') NOT NULL DEFAULT 'hello'
+  CREATE DOMAIN ss1 AS ss
+  CREATE VIEW test AS SELECT 'hello'::ss AS test
+  CREATE TABLE t(a ss1);
+\dD regress_schema_3.*
+                                                    List of domains
+      Schema      | Name |        Type         | Collation | Nullable |    Default    |             Check              
+------------------+------+---------------------+-----------+----------+---------------+--------------------------------
+ regress_schema_3 | ss   | text                |           | not null | 'hello'::text | CHECK (VALUE <> 'hello'::text)
+ regress_schema_3 | ss1  | regress_schema_3.ss |           |          | 'hello'::text | 
+(2 rows)
+
+DROP SCHEMA regress_schema_2 CASCADE;
+NOTICE:  drop cascades to 2 other objects
+DETAIL:  drop cascades to type regress_schema_2.ss
+drop cascades to table regress_schema_2.t
+DROP SCHEMA regress_schema_3 CASCADE;
+NOTICE:  drop cascades to 4 other objects
+DETAIL:  drop cascades to type regress_schema_3.ss
+drop cascades to type regress_schema_3.ss1
+drop cascades to view regress_schema_3.test
+drop cascades to table regress_schema_3.t
 -- Clean up
 DROP ROLE regress_create_schema_role;
diff --git a/src/test/regress/sql/create_schema.sql b/src/test/regress/sql/create_schema.sql
index b3dc1cfd758..3028148e96b 100644
--- a/src/test/regress/sql/create_schema.sql
+++ b/src/test/regress/sql/create_schema.sql
@@ -71,5 +71,40 @@ CREATE SCHEMA regress_schema_1 AUTHORIZATION CURRENT_ROLE
 DROP SCHEMA regress_schema_1 CASCADE;
 RESET ROLE;
 
+-- Cases where the schema creation with domain.
+--fail. cannot CREATE DOMAIN to other schema
+CREATE SCHEMA regress_schema_2 AUTHORIZATION CURRENT_ROLE
+  CREATE DOMAIN public.ss AS TEXT NOT NULL DEFAULT 'hello' CONSTRAINT nn CHECK (VALUE <> 'hello')
+  CREATE TABLE t(a ss);
+--fail. cannot CREATE DOMAIN to other schema
+CREATE SCHEMA regress_schema_2 AUTHORIZATION CURRENT_ROLE
+  CREATE DOMAIN postgres.public.ss AS TEXT NOT NULL CONSTRAINT nn CHECK (VALUE <> 'hello') DEFAULT 'hello'
+  CREATE TABLE t(a ss);
+
+--fail. improper qualified name
+CREATE SCHEMA regress_schema_2 CREATE DOMAIN ss.postgres.regress_schema_2.ss AS TEXT;
+
+--fail. Execute subcommands in order; we do not implicitly reorder them.
+CREATE SCHEMA regress_schema_2 AUTHORIZATION CURRENT_ROLE
+  CREATE DOMAIN ss1 AS ss
+  CREATE DOMAIN ss AS TEXT;
+
+--ok, qualified schema name for domain should be same as the created schema.
+CREATE SCHEMA regress_schema_2 AUTHORIZATION CURRENT_ROLE
+  CREATE DOMAIN regress_schema_2.ss AS TEXT NOT NULL CONSTRAINT nn CHECK (VALUE <> 'hello') DEFAULT 'hello' COLLATE "C"
+  CREATE TABLE t(a regress_schema_2.ss);
+\dD regress_schema_2.*
+
+--ok, no qualified schema name for domain.
+CREATE SCHEMA regress_schema_3 AUTHORIZATION CURRENT_ROLE
+  CREATE DOMAIN ss AS TEXT  CONSTRAINT nn CHECK (VALUE <> 'hello') NOT NULL DEFAULT 'hello'
+  CREATE DOMAIN ss1 AS ss
+  CREATE VIEW test AS SELECT 'hello'::ss AS test
+  CREATE TABLE t(a ss1);
+\dD regress_schema_3.*
+
+DROP SCHEMA regress_schema_2 CASCADE;
+DROP SCHEMA regress_schema_3 CASCADE;
+
 -- Clean up
 DROP ROLE regress_create_schema_role;
-- 
2.34.1



  [text/x-patch] v7-0003-CREATE-SCHEMA-CREATE-COLLATION.patch (15.5K, 3-v7-0003-CREATE-SCHEMA-CREATE-COLLATION.patch)
  download | inline diff:
From cf95a41428b022e6361f9a4a0e00a1a9474a34b6 Mon Sep 17 00:00:00 2001
From: jian he <[email protected]>
Date: Tue, 26 Aug 2025 12:43:12 +0800
Subject: [PATCH v7 3/4] CREATE SCHEMA CREATE COLLATION

SQL standard allow collation to be specified with CREATE SCHEMA
statement. This patch adds support in PostgreSQL for that.

For example:
    CREATE SCHEMA schema_name CREATE COLLATION coll_icu_und FROM "und-x-icu";

The collation will be created within the to be created schema.
The collation name can be schema-qualified or database-qualified,
however it's not allowed to let collation create within a different schema.

Note: src/bin/psql/tab-complete.in.c changes seems incorrect.

Discussion: https://postgr.es/m/CALdSSPh4jUSDsWu3K58hjO60wnTRR0DuO4CKRcwa8EVuOSfXxg@mail.gmail.com
---
 doc/src/sgml/ref/create_schema.sgml           |  5 ++-
 src/backend/catalog/objectaddress.c           | 16 +++++++
 src/backend/parser/gram.y                     |  1 +
 src/backend/parser/parse_utilcmd.c            | 32 ++++++++++++++
 src/bin/psql/tab-complete.in.c                |  8 ++--
 src/include/catalog/objectaddress.h           |  1 +
 .../expected/create_schema.out                |  4 +-
 .../test_ddl_deparse/sql/create_schema.sql    |  3 +-
 .../regress/expected/collate.icu.utf8.out     | 22 ++++++++++
 src/test/regress/expected/create_schema.out   | 43 +++++++++++++++++++
 src/test/regress/sql/collate.icu.utf8.sql     | 12 ++++++
 src/test/regress/sql/create_schema.sql        | 27 ++++++++++++
 12 files changed, 166 insertions(+), 8 deletions(-)

diff --git a/doc/src/sgml/ref/create_schema.sgml b/doc/src/sgml/ref/create_schema.sgml
index 79186d2b936..faf99c3399d 100644
--- a/doc/src/sgml/ref/create_schema.sgml
+++ b/doc/src/sgml/ref/create_schema.sgml
@@ -100,8 +100,9 @@ CREATE SCHEMA IF NOT EXISTS AUTHORIZATION <replaceable class="parameter">role_sp
       <listitem>
        <para>
         An SQL statement defining an object to be created within the
-        schema. Currently, only <command>CREATE DOMAIN</command>
-        <command>CREATE TABLE</command>, <command>CREATE VIEW</command>, <command>CREATE
+        schema. Currently, only <command>CREATE COLLATION</command>,
+        <command>CREATE DOMAIN</command>, <command>CREATE TABLE</command>,
+        <command>CREATE VIEW</command>, <command>CREATE
         INDEX</command>, <command>CREATE SEQUENCE</command>, <command>CREATE
         TRIGGER</command> and <command>GRANT</command> are accepted as clauses
         within <command>CREATE SCHEMA</command>. Other kinds of objects may
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 0102c9984e7..55b65dd08a3 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -2622,6 +2622,22 @@ read_objtype_from_string(const char *objtype)
 	return -1;					/* keep compiler quiet */
 }
 
+const char *
+stringify_objtype(ObjectType objtype)
+{
+	int			i;
+
+	for (i = 0; i < lengthof(ObjectTypeMap); i++)
+	{
+		if (ObjectTypeMap[i].tm_type == objtype)
+			return ObjectTypeMap[i].tm_name;
+	}
+	ereport(ERROR,
+			errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+			errmsg("unrecognized object type %d", objtype));
+
+	return NULL;					/* keep compiler quiet */
+}
 /*
  * Interfaces to reference fields of ObjectPropertyType
  */
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 3aca508b08f..30c4a567502 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -1627,6 +1627,7 @@ schema_stmt:
 			| GrantStmt
 			| ViewStmt
 			| CreateDomainStmt
+			| DefineStmt
 		;
 
 
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 218ec6f0982..431023ee734 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -61,6 +61,7 @@
 #include "rewrite/rewriteManip.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
+#include "utils/formatting.h"
 #include "utils/lsyscache.h"
 #include "utils/partcache.h"
 #include "utils/rel.h"
@@ -4171,6 +4172,37 @@ transformCreateSchemaStmtElements(ParseState *pstate, List *schemaElts,
 				}
 				break;
 
+			case T_DefineStmt:
+				{
+					char	   *coll_schema = NULL;
+					char	   *collName;
+					DefineStmt *stmt = (DefineStmt *) element;
+
+					if (stmt->kind != OBJECT_COLLATION)
+						ereport(ERROR,
+								errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								errmsg("CREATE SCHEMA ... CREATE %s currently not supported",
+									   asc_toupper(stringify_objtype(stmt->kind),
+												   strlen(stringify_objtype(stmt->kind)))));
+
+					DeconstructQualifiedName(stmt->defnames, &coll_schema, &collName);
+					if (coll_schema && strcmp(coll_schema, schemaName) != 0)
+						ereport(ERROR,
+								errcode(ERRCODE_INVALID_SCHEMA_DEFINITION),
+								errmsg("CREATE specifies a schema (%s) "
+									   "different from the one being created (%s)",
+									   schemaName, coll_schema));
+
+					elements = lappend(elements, element);
+				}
+				break;
+			case T_CompositeTypeStmt:
+			case T_CreateEnumStmt:
+			case T_CreateRangeStmt:
+				ereport(ERROR,
+						errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						errmsg("CREATE SCHEMA ... CREATE TYPE currently not supported"));
+				break;
 			default:
 				elog(ERROR, "unrecognized node type: %d",
 					 (int) nodeTag(element));
diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c
index d7a8c769e35..b6beb868eb2 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -2178,7 +2178,7 @@ match_previous_words(int pattern_id,
 	{
 		/* only some object types can be created as part of CREATE SCHEMA */
 		if (HeadMatches("CREATE", "SCHEMA"))
-			COMPLETE_WITH("TABLE", "VIEW", "INDEX", "SEQUENCE", "TRIGGER", "DOMAIN",
+			COMPLETE_WITH("TABLE", "VIEW", "INDEX", "SEQUENCE", "TRIGGER", "DOMAIN", "COLLATION",
 			/* for INDEX and TABLE/SEQUENCE, respectively */
 						  "UNIQUE", "UNLOGGED");
 		else
@@ -3370,10 +3370,10 @@ match_previous_words(int pattern_id,
 	else if (Matches("CREATE", "ACCESS", "METHOD", MatchAny, "TYPE", MatchAny))
 		COMPLETE_WITH("HANDLER");
 
-	/* CREATE COLLATION */
-	else if (Matches("CREATE", "COLLATION", MatchAny))
+	/* CREATE COLLATION --- is allowed inside CREATE SCHEMA, so use TailMatches */
+	else if (TailMatches("CREATE", "COLLATION", MatchAny))
 		COMPLETE_WITH("(", "FROM");
-	else if (Matches("CREATE", "COLLATION", MatchAny, "FROM"))
+	else if (TailMatches("CREATE", "COLLATION", MatchAny, "FROM"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_collations);
 	else if (HeadMatches("CREATE", "COLLATION", MatchAny, "(*"))
 	{
diff --git a/src/include/catalog/objectaddress.h b/src/include/catalog/objectaddress.h
index 630434b73cf..96e6abc9ffd 100644
--- a/src/include/catalog/objectaddress.h
+++ b/src/include/catalog/objectaddress.h
@@ -79,6 +79,7 @@ extern char *getObjectDescription(const ObjectAddress *object,
 extern char *getObjectDescriptionOids(Oid classid, Oid objid);
 
 extern int	read_objtype_from_string(const char *objtype);
+extern const char *stringify_objtype(ObjectType objtype);
 extern char *getObjectTypeDescription(const ObjectAddress *object,
 									  bool missing_ok);
 extern char *getObjectIdentity(const ObjectAddress *object,
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 d73c4702051..2a905b28600 100644
--- a/src/test/modules/test_ddl_deparse/expected/create_schema.out
+++ b/src/test/modules/test_ddl_deparse/expected/create_schema.out
@@ -14,8 +14,10 @@ NOTICE:  schema "baz" already exists, skipping
 CREATE SCHEMA element_test
   CREATE TABLE foo (id int)
   CREATE VIEW bar AS SELECT * FROM foo
-  CREATE DOMAIN d1 AS INT;
+  CREATE DOMAIN d1 AS INT
+  CREATE COLLATION coll (LOCALE="C");
 NOTICE:  DDL test: type simple, tag CREATE SCHEMA
 NOTICE:  DDL test: type simple, tag CREATE TABLE
 NOTICE:  DDL test: type simple, tag CREATE VIEW
 NOTICE:  DDL test: type simple, tag CREATE DOMAIN
+NOTICE:  DDL test: type simple, tag CREATE COLLATION
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 57ada462070..9581935160e 100644
--- a/src/test/modules/test_ddl_deparse/sql/create_schema.sql
+++ b/src/test/modules/test_ddl_deparse/sql/create_schema.sql
@@ -15,4 +15,5 @@ CREATE SCHEMA IF NOT EXISTS baz;
 CREATE SCHEMA element_test
   CREATE TABLE foo (id int)
   CREATE VIEW bar AS SELECT * FROM foo
-  CREATE DOMAIN d1 AS INT;
+  CREATE DOMAIN d1 AS INT
+  CREATE COLLATION coll (LOCALE="C");
diff --git a/src/test/regress/expected/collate.icu.utf8.out b/src/test/regress/expected/collate.icu.utf8.out
index 69805d4b9ec..4c303385447 100644
--- a/src/test/regress/expected/collate.icu.utf8.out
+++ b/src/test/regress/expected/collate.icu.utf8.out
@@ -2690,6 +2690,28 @@ SELECT * FROM t5 ORDER BY c ASC, a ASC;
  3 | d1 | d1
 (3 rows)
 
+--CREATE SCHEMA CREATE COLLATION
+--fail. Execute subcommands in order; we do not implicitly reorder them.
+CREATE SCHEMA regress_schema_4 AUTHORIZATION CURRENT_ROLE
+  CREATE TABLE tts(a TEXT COLLATE coll_icu_und)
+  CREATE COLLATION coll_icu_und FROM "und-x-icu";
+ERROR:  collation "coll_icu_und" for encoding "UTF8" does not exist
+LINE 2:   CREATE TABLE tts(a TEXT COLLATE coll_icu_und)
+                                  ^
+CREATE SCHEMA regress_schema_4
+  CREATE COLLATION coll_icu_und FROM "und-x-icu"
+  CREATE TABLE tts(a TEXT COLLATE coll_icu_und);
+\dO regress_schema_4.*
+                                         List of collations
+      Schema      |     Name     | Provider | Collate | Ctype | Locale | ICU Rules | Deterministic? 
+------------------+--------------+----------+---------+-------+--------+-----------+----------------
+ regress_schema_4 | coll_icu_und | icu      |         |       | und    |           | yes
+(1 row)
+
+DROP SCHEMA regress_schema_4 CASCADE;
+NOTICE:  drop cascades to 2 other objects
+DETAIL:  drop cascades to collation regress_schema_4.coll_icu_und
+drop cascades to table regress_schema_4.tts
 -- cleanup
 RESET search_path;
 SET client_min_messages TO warning;
diff --git a/src/test/regress/expected/create_schema.out b/src/test/regress/expected/create_schema.out
index d6718a9f519..91f586606e7 100644
--- a/src/test/regress/expected/create_schema.out
+++ b/src/test/regress/expected/create_schema.out
@@ -177,6 +177,41 @@ CREATE SCHEMA regress_schema_3 AUTHORIZATION CURRENT_ROLE
  regress_schema_3 | ss1  | regress_schema_3.ss |           |          | 'hello'::text | 
 (2 rows)
 
+-- Cases where the schema creation with collations
+--fail. cannot CREATE DOMAIN to other schema
+CREATE SCHEMA regress_schema_4 AUTHORIZATION CURRENT_ROLE
+  CREATE COLLATION public.coll_icu_und FROM "und-x-icu";
+ERROR:  CREATE specifies a schema (regress_schema_4) different from the one being created (public)
+--fail. improper qualified name
+CREATE SCHEMA regress_schema_4 AUTHORIZATION CURRENT_ROLE
+  CREATE COLLATION postgres.public.coll_icu_und FROM "und-x-icu";
+ERROR:  cross-database references are not implemented: postgres.public.coll_icu_und
+--fail. only support collation object for DefineStmt node
+CREATE SCHEMA regress_schema_4 AUTHORIZATION CURRENT_ROLE
+  CREATE AGGREGATE balk(int4)(SFUNC = int4_sum(int8, int4),STYPE = int8, PARALLEL = SAFE, INITCOND = '0');
+ERROR:  CREATE SCHEMA ... CREATE AGGREGATE currently not supported
+--ok, qualified schema name for domain should be same as the created schema
+CREATE SCHEMA regress_schema_4 AUTHORIZATION CURRENT_ROLE
+  CREATE COLLATION regress_schema_4.coll (LOCALE="C")
+  CREATE TABLE t(a TEXT COLLATE regress_schema_4.coll);
+\dO regress_schema_4.*
+                                     List of collations
+      Schema      | Name | Provider | Collate | Ctype | Locale | ICU Rules | Deterministic? 
+------------------+------+----------+---------+-------+--------+-----------+----------------
+ regress_schema_4 | coll | libc     | C       | C     |        |           | yes
+(1 row)
+
+--ok, no qualified schema name for collation
+CREATE SCHEMA regress_schema_5 AUTHORIZATION CURRENT_ROLE
+  CREATE COLLATION coll (LOCALE="C")
+  CREATE TABLE t(a TEXT COLLATE coll);
+\dO regress_schema_5.*
+                                     List of collations
+      Schema      | Name | Provider | Collate | Ctype | Locale | ICU Rules | Deterministic? 
+------------------+------+----------+---------+-------+--------+-----------+----------------
+ regress_schema_5 | coll | libc     | C       | C     |        |           | yes
+(1 row)
+
 DROP SCHEMA regress_schema_2 CASCADE;
 NOTICE:  drop cascades to 2 other objects
 DETAIL:  drop cascades to type regress_schema_2.ss
@@ -187,5 +222,13 @@ DETAIL:  drop cascades to type regress_schema_3.ss
 drop cascades to type regress_schema_3.ss1
 drop cascades to view regress_schema_3.test
 drop cascades to table regress_schema_3.t
+DROP SCHEMA regress_schema_4 CASCADE;
+NOTICE:  drop cascades to 2 other objects
+DETAIL:  drop cascades to collation regress_schema_4.coll
+drop cascades to table regress_schema_4.t
+DROP SCHEMA regress_schema_5 CASCADE;
+NOTICE:  drop cascades to 2 other objects
+DETAIL:  drop cascades to collation regress_schema_5.coll
+drop cascades to table regress_schema_5.t
 -- Clean up
 DROP ROLE regress_create_schema_role;
diff --git a/src/test/regress/sql/collate.icu.utf8.sql b/src/test/regress/sql/collate.icu.utf8.sql
index dbc190227d0..06a46662f7f 100644
--- a/src/test/regress/sql/collate.icu.utf8.sql
+++ b/src/test/regress/sql/collate.icu.utf8.sql
@@ -997,6 +997,18 @@ INSERT INTO t5 (a, b) values (1, 'D1'), (2, 'D2'), (3, 'd1');
 -- rewriting.)
 SELECT * FROM t5 ORDER BY c ASC, a ASC;
 
+--CREATE SCHEMA CREATE COLLATION
+--fail. Execute subcommands in order; we do not implicitly reorder them.
+CREATE SCHEMA regress_schema_4 AUTHORIZATION CURRENT_ROLE
+  CREATE TABLE tts(a TEXT COLLATE coll_icu_und)
+  CREATE COLLATION coll_icu_und FROM "und-x-icu";
+
+CREATE SCHEMA regress_schema_4
+  CREATE COLLATION coll_icu_und FROM "und-x-icu"
+  CREATE TABLE tts(a TEXT COLLATE coll_icu_und);
+\dO regress_schema_4.*
+DROP SCHEMA regress_schema_4 CASCADE;
+
 
 -- cleanup
 RESET search_path;
diff --git a/src/test/regress/sql/create_schema.sql b/src/test/regress/sql/create_schema.sql
index 3028148e96b..608defcd5e2 100644
--- a/src/test/regress/sql/create_schema.sql
+++ b/src/test/regress/sql/create_schema.sql
@@ -103,8 +103,35 @@ CREATE SCHEMA regress_schema_3 AUTHORIZATION CURRENT_ROLE
   CREATE TABLE t(a ss1);
 \dD regress_schema_3.*
 
+-- Cases where the schema creation with collations
+--fail. cannot CREATE DOMAIN to other schema
+CREATE SCHEMA regress_schema_4 AUTHORIZATION CURRENT_ROLE
+  CREATE COLLATION public.coll_icu_und FROM "und-x-icu";
+
+--fail. improper qualified name
+CREATE SCHEMA regress_schema_4 AUTHORIZATION CURRENT_ROLE
+  CREATE COLLATION postgres.public.coll_icu_und FROM "und-x-icu";
+
+--fail. only support collation object for DefineStmt node
+CREATE SCHEMA regress_schema_4 AUTHORIZATION CURRENT_ROLE
+  CREATE AGGREGATE balk(int4)(SFUNC = int4_sum(int8, int4),STYPE = int8, PARALLEL = SAFE, INITCOND = '0');
+
+--ok, qualified schema name for domain should be same as the created schema
+CREATE SCHEMA regress_schema_4 AUTHORIZATION CURRENT_ROLE
+  CREATE COLLATION regress_schema_4.coll (LOCALE="C")
+  CREATE TABLE t(a TEXT COLLATE regress_schema_4.coll);
+\dO regress_schema_4.*
+
+--ok, no qualified schema name for collation
+CREATE SCHEMA regress_schema_5 AUTHORIZATION CURRENT_ROLE
+  CREATE COLLATION coll (LOCALE="C")
+  CREATE TABLE t(a TEXT COLLATE coll);
+\dO regress_schema_5.*
+
 DROP SCHEMA regress_schema_2 CASCADE;
 DROP SCHEMA regress_schema_3 CASCADE;
+DROP SCHEMA regress_schema_4 CASCADE;
+DROP SCHEMA regress_schema_5 CASCADE;
 
 -- Clean up
 DROP ROLE regress_create_schema_role;
-- 
2.34.1



  [text/x-patch] v7-0004-CREATE-SCHEMA-CREATE-TYPE.patch (15.3K, 4-v7-0004-CREATE-SCHEMA-CREATE-TYPE.patch)
  download | inline diff:
From 8c1aae1069a7b8507c0f26a70561e44675599b85 Mon Sep 17 00:00:00 2001
From: jian he <[email protected]>
Date: Tue, 26 Aug 2025 12:27:56 +0800
Subject: [PATCH v7 4/4] CREATE SCHEMA CREATE TYPE

SQL standard allow type to be specified with CREATE SCHEMA
statement. This patch adds support in PostgreSQL for that.

For example:
    CREATE SCHEMA schema_name CREATE TYPE ss;

The type will be created within the to be created schema.  The type name can be
schema-qualified or database-qualified, however it's not allowed to let type
create within a different schema.

TODO: tab complete for CREATE SCHEMA ... CREATE TYPE seems tricky

Discussion: https://postgr.es/m/CALdSSPh4jUSDsWu3K58hjO60wnTRR0DuO4CKRcwa8EVuOSfXxg@mail.gmail.com
---
 doc/src/sgml/ref/create_schema.sgml           |  3 +-
 src/backend/parser/parse_utilcmd.c            | 44 +++++++++-
 .../expected/create_schema.out                | 10 ++-
 .../test_ddl_deparse/sql/create_schema.sql    |  6 +-
 src/test/regress/expected/create_schema.out   | 83 +++++++++++++++++++
 src/test/regress/expected/create_type.out     | 12 +++
 src/test/regress/sql/create_schema.sql        | 39 +++++++++
 src/test/regress/sql/create_type.sql          | 12 +++
 8 files changed, 202 insertions(+), 7 deletions(-)

diff --git a/doc/src/sgml/ref/create_schema.sgml b/doc/src/sgml/ref/create_schema.sgml
index faf99c3399d..238fd949934 100644
--- a/doc/src/sgml/ref/create_schema.sgml
+++ b/doc/src/sgml/ref/create_schema.sgml
@@ -104,7 +104,8 @@ CREATE SCHEMA IF NOT EXISTS AUTHORIZATION <replaceable class="parameter">role_sp
         <command>CREATE DOMAIN</command>, <command>CREATE TABLE</command>,
         <command>CREATE VIEW</command>, <command>CREATE
         INDEX</command>, <command>CREATE SEQUENCE</command>, <command>CREATE
-        TRIGGER</command> and <command>GRANT</command> are accepted as clauses
+        TRIGGER</command>, <command>CREATE TYPE</command> and
+        <command>GRANT</command> are accepted as clauses
         within <command>CREATE SCHEMA</command>. Other kinds of objects may
         be created in separate commands after the schema is created.
        </para>
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 431023ee734..d3d9a09242f 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -4178,7 +4178,7 @@ transformCreateSchemaStmtElements(ParseState *pstate, List *schemaElts,
 					char	   *collName;
 					DefineStmt *stmt = (DefineStmt *) element;
 
-					if (stmt->kind != OBJECT_COLLATION)
+					if (stmt->kind != OBJECT_COLLATION && stmt->kind != OBJECT_TYPE)
 						ereport(ERROR,
 								errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 								errmsg("CREATE SCHEMA ... CREATE %s currently not supported",
@@ -4196,12 +4196,48 @@ transformCreateSchemaStmtElements(ParseState *pstate, List *schemaElts,
 					elements = lappend(elements, element);
 				}
 				break;
+
 			case T_CompositeTypeStmt:
+				{
+					CompositeTypeStmt *stmt = (CompositeTypeStmt *) element;
+
+					checkSchemaName(pstate, schemaName, stmt->typevar);
+					elements = lappend(elements, element);
+				}
+				break;
+
 			case T_CreateEnumStmt:
+				{
+					char	   *schema = NULL;
+					char	   *typname;
+					CreateEnumStmt *stmt = (CreateEnumStmt *) element;
+					DeconstructQualifiedName(stmt->typeName, &schema, &typname);
+					if (schema && strcmp(schema, schemaName) != 0)
+						ereport(ERROR,
+								errcode(ERRCODE_INVALID_SCHEMA_DEFINITION),
+								errmsg("CREATE specifies a schema (%s) "
+									   "different from the one being created (%s)",
+									   schemaName, schema));
+
+					elements = lappend(elements, element);
+				}
+				break;
+
 			case T_CreateRangeStmt:
-				ereport(ERROR,
-						errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-						errmsg("CREATE SCHEMA ... CREATE TYPE currently not supported"));
+				{
+					char	   *schema = NULL;
+					char	   *typname;
+					CreateRangeStmt *stmt = (CreateRangeStmt *) element;
+					DeconstructQualifiedName(stmt->typeName, &schema, &typname);
+					if (schema && strcmp(schema, schemaName) != 0)
+						ereport(ERROR,
+								errcode(ERRCODE_INVALID_SCHEMA_DEFINITION),
+								errmsg("CREATE specifies a schema (%s) "
+									   "different from the one being created (%s)",
+									   schemaName, schema));
+
+					elements = lappend(elements, element);
+				}
 				break;
 			default:
 				elog(ERROR, "unrecognized node type: %d",
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 2a905b28600..b2c85682b20 100644
--- a/src/test/modules/test_ddl_deparse/expected/create_schema.out
+++ b/src/test/modules/test_ddl_deparse/expected/create_schema.out
@@ -15,9 +15,17 @@ CREATE SCHEMA element_test
   CREATE TABLE foo (id int)
   CREATE VIEW bar AS SELECT * FROM foo
   CREATE DOMAIN d1 AS INT
-  CREATE COLLATION coll (LOCALE="C");
+  CREATE COLLATION coll (LOCALE="C")
+  CREATE TYPE floatrange AS RANGE (subtype = float8, subtype_diff = float8mi)
+  CREATE TYPE ss AS (a int)
+  CREATE TYPE sss
+  CREATE TYPE rainbow AS ENUM ('red', 'orange');
 NOTICE:  DDL test: type simple, tag CREATE SCHEMA
 NOTICE:  DDL test: type simple, tag CREATE TABLE
 NOTICE:  DDL test: type simple, tag CREATE VIEW
 NOTICE:  DDL test: type simple, tag CREATE DOMAIN
 NOTICE:  DDL test: type simple, tag CREATE COLLATION
+NOTICE:  DDL test: type simple, tag CREATE TYPE
+NOTICE:  DDL test: type simple, tag CREATE TYPE
+NOTICE:  DDL test: type simple, tag CREATE TYPE
+NOTICE:  DDL test: type simple, tag CREATE TYPE
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 9581935160e..5ce79b524bd 100644
--- a/src/test/modules/test_ddl_deparse/sql/create_schema.sql
+++ b/src/test/modules/test_ddl_deparse/sql/create_schema.sql
@@ -16,4 +16,8 @@ CREATE SCHEMA element_test
   CREATE TABLE foo (id int)
   CREATE VIEW bar AS SELECT * FROM foo
   CREATE DOMAIN d1 AS INT
-  CREATE COLLATION coll (LOCALE="C");
+  CREATE COLLATION coll (LOCALE="C")
+  CREATE TYPE floatrange AS RANGE (subtype = float8, subtype_diff = float8mi)
+  CREATE TYPE ss AS (a int)
+  CREATE TYPE sss
+  CREATE TYPE rainbow AS ENUM ('red', 'orange');
diff --git a/src/test/regress/expected/create_schema.out b/src/test/regress/expected/create_schema.out
index 91f586606e7..6409aee7361 100644
--- a/src/test/regress/expected/create_schema.out
+++ b/src/test/regress/expected/create_schema.out
@@ -212,6 +212,76 @@ CREATE SCHEMA regress_schema_5 AUTHORIZATION CURRENT_ROLE
  regress_schema_5 | coll | libc     | C       | C     |        |           | yes
 (1 row)
 
+-----CREATE SCHEMA CREATE TYPE
+--fail. cannot CREATE TYPE to other schema
+CREATE SCHEMA regress_schema_6 CREATE TYPE public.ss AS (a int);
+ERROR:  CREATE specifies a schema (public) different from the one being created (regress_schema_6)
+LINE 1: CREATE SCHEMA regress_schema_6 CREATE TYPE public.ss AS (a i...
+                                                   ^
+CREATE SCHEMA regress_schema_6 CREATE TYPE public.ss;
+ERROR:  CREATE specifies a schema (regress_schema_6) different from the one being created (public)
+CREATE SCHEMA regress_schema_6 CREATE TYPE public.rainbow AS ENUM ('red', 'orange');
+ERROR:  CREATE specifies a schema (regress_schema_6) different from the one being created (public)
+CREATE SCHEMA regress_schema_6 CREATE TYPE public.floatrange AS RANGE (subtype = float8, subtype_diff = float8mi);
+ERROR:  CREATE specifies a schema (regress_schema_6) different from the one being created (public)
+--fail. improper qualified name
+CREATE SCHEMA regress_schema_6 AUTHORIZATION CURRENT_ROLE
+  CREATE TYPE postgres.public.floatrange AS RANGE (subtype = float8, subtype_diff = float8mi);
+ERROR:  cross-database references are not implemented: postgres.public.floatrange
+--all the following 4 should error, because we execute subcommands in order
+CREATE SCHEMA regress_schema_6 CREATE TABLE tts(a ss) CREATE TYPE ss;
+ERROR:  type "ss" does not exist
+LINE 1: CREATE SCHEMA regress_schema_6 CREATE TABLE tts(a ss) CREATE...
+                                                          ^
+CREATE SCHEMA regress_schema_6 CREATE TABLE tts(a ss) CREATE TYPE ss AS (a int);
+ERROR:  type "ss" does not exist
+LINE 1: CREATE SCHEMA regress_schema_6 CREATE TABLE tts(a ss) CREATE...
+                                                          ^
+CREATE SCHEMA regress_schema_6 CREATE TABLE tts(a rainbow) CREATE TYPE rainbow AS ENUM ('red', 'orange');
+ERROR:  type "rainbow" does not exist
+LINE 1: CREATE SCHEMA regress_schema_6 CREATE TABLE tts(a rainbow) C...
+                                                          ^
+CREATE SCHEMA regress_schema_6
+  CREATE TABLE tts(a floatrange)
+  CREATE TYPE floatrange AS RANGE (subtype = float8, subtype_diff = float8mi);
+ERROR:  type "floatrange" does not exist
+LINE 2:   CREATE TABLE tts(a floatrange)
+                             ^
+--ok
+CREATE SCHEMA regress_schema_6
+  CREATE TYPE floatrange AS RANGE (subtype = float8, subtype_diff = float8mi)
+  CREATE TYPE ss AS (a int)
+  CREATE TYPE sss
+  CREATE TYPE rainbow AS ENUM ('red', 'orange');
+\dT regress_schema_6.*
+                        List of data types
+      Schema      |               Name               | Description 
+------------------+----------------------------------+-------------
+ regress_schema_6 | regress_schema_6.floatmultirange | 
+ regress_schema_6 | regress_schema_6.floatrange      | 
+ regress_schema_6 | regress_schema_6.rainbow         | 
+ regress_schema_6 | regress_schema_6.ss              | 
+ regress_schema_6 | regress_schema_6.sss             | 
+(5 rows)
+
+--schema qualified, ok
+CREATE SCHEMA regress_schema_7
+  CREATE TYPE regress_schema_7.floatrange AS RANGE (subtype = float8, subtype_diff = float8mi)
+  CREATE TYPE regress_schema_7.ss AS (a int)
+  CREATE TYPE regress_schema_7.sss
+  CREATE TYPE regress_schema_7.rainbow AS ENUM ('red', 'orange')
+  CREATE TABLE t(a floatrange, b ss, c rainbow);
+\dT regress_schema_7.*
+                        List of data types
+      Schema      |               Name               | Description 
+------------------+----------------------------------+-------------
+ regress_schema_7 | regress_schema_7.floatmultirange | 
+ regress_schema_7 | regress_schema_7.floatrange      | 
+ regress_schema_7 | regress_schema_7.rainbow         | 
+ regress_schema_7 | regress_schema_7.ss              | 
+ regress_schema_7 | regress_schema_7.sss             | 
+(5 rows)
+
 DROP SCHEMA regress_schema_2 CASCADE;
 NOTICE:  drop cascades to 2 other objects
 DETAIL:  drop cascades to type regress_schema_2.ss
@@ -230,5 +300,18 @@ DROP SCHEMA regress_schema_5 CASCADE;
 NOTICE:  drop cascades to 2 other objects
 DETAIL:  drop cascades to collation regress_schema_5.coll
 drop cascades to table regress_schema_5.t
+DROP SCHEMA regress_schema_6 CASCADE;
+NOTICE:  drop cascades to 4 other objects
+DETAIL:  drop cascades to type regress_schema_6.floatrange
+drop cascades to type regress_schema_6.ss
+drop cascades to type regress_schema_6.sss
+drop cascades to type regress_schema_6.rainbow
+DROP SCHEMA regress_schema_7 CASCADE;
+NOTICE:  drop cascades to 5 other objects
+DETAIL:  drop cascades to type regress_schema_7.floatrange
+drop cascades to type regress_schema_7.ss
+drop cascades to type regress_schema_7.sss
+drop cascades to type regress_schema_7.rainbow
+drop cascades to table regress_schema_7.t
 -- Clean up
 DROP ROLE regress_create_schema_role;
diff --git a/src/test/regress/expected/create_type.out b/src/test/regress/expected/create_type.out
index 5181c4290b4..28eadcde20d 100644
--- a/src/test/regress/expected/create_type.out
+++ b/src/test/regress/expected/create_type.out
@@ -35,6 +35,18 @@ CREATE FUNCTION int44out(city_budget)
 NOTICE:  argument type city_budget is only a shell
 LINE 1: CREATE FUNCTION int44out(city_budget)
                                  ^
+----- CREATE SCHEMA CREATE TYPE test
+--error. cannot CREATE TYPE to other schema
+CREATE SCHEMA regress_schema_1
+   CREATE TYPE public.widget (
+      internallength = 24,
+      input = widget_in,
+      output = widget_out,
+      typmod_in = numerictypmodin,
+      typmod_out = numerictypmodout,
+      alignment = double
+   );
+ERROR:  CREATE specifies a schema (regress_schema_1) different from the one being created (public)
 CREATE TYPE widget (
    internallength = 24,
    input = widget_in,
diff --git a/src/test/regress/sql/create_schema.sql b/src/test/regress/sql/create_schema.sql
index 608defcd5e2..645f3308fb6 100644
--- a/src/test/regress/sql/create_schema.sql
+++ b/src/test/regress/sql/create_schema.sql
@@ -128,10 +128,49 @@ CREATE SCHEMA regress_schema_5 AUTHORIZATION CURRENT_ROLE
   CREATE TABLE t(a TEXT COLLATE coll);
 \dO regress_schema_5.*
 
+
+-----CREATE SCHEMA CREATE TYPE
+--fail. cannot CREATE TYPE to other schema
+CREATE SCHEMA regress_schema_6 CREATE TYPE public.ss AS (a int);
+CREATE SCHEMA regress_schema_6 CREATE TYPE public.ss;
+CREATE SCHEMA regress_schema_6 CREATE TYPE public.rainbow AS ENUM ('red', 'orange');
+CREATE SCHEMA regress_schema_6 CREATE TYPE public.floatrange AS RANGE (subtype = float8, subtype_diff = float8mi);
+
+--fail. improper qualified name
+CREATE SCHEMA regress_schema_6 AUTHORIZATION CURRENT_ROLE
+  CREATE TYPE postgres.public.floatrange AS RANGE (subtype = float8, subtype_diff = float8mi);
+
+--all the following 4 should error, because we execute subcommands in order
+CREATE SCHEMA regress_schema_6 CREATE TABLE tts(a ss) CREATE TYPE ss;
+CREATE SCHEMA regress_schema_6 CREATE TABLE tts(a ss) CREATE TYPE ss AS (a int);
+CREATE SCHEMA regress_schema_6 CREATE TABLE tts(a rainbow) CREATE TYPE rainbow AS ENUM ('red', 'orange');
+CREATE SCHEMA regress_schema_6
+  CREATE TABLE tts(a floatrange)
+  CREATE TYPE floatrange AS RANGE (subtype = float8, subtype_diff = float8mi);
+
+--ok
+CREATE SCHEMA regress_schema_6
+  CREATE TYPE floatrange AS RANGE (subtype = float8, subtype_diff = float8mi)
+  CREATE TYPE ss AS (a int)
+  CREATE TYPE sss
+  CREATE TYPE rainbow AS ENUM ('red', 'orange');
+\dT regress_schema_6.*
+
+--schema qualified, ok
+CREATE SCHEMA regress_schema_7
+  CREATE TYPE regress_schema_7.floatrange AS RANGE (subtype = float8, subtype_diff = float8mi)
+  CREATE TYPE regress_schema_7.ss AS (a int)
+  CREATE TYPE regress_schema_7.sss
+  CREATE TYPE regress_schema_7.rainbow AS ENUM ('red', 'orange')
+  CREATE TABLE t(a floatrange, b ss, c rainbow);
+\dT regress_schema_7.*
+
 DROP SCHEMA regress_schema_2 CASCADE;
 DROP SCHEMA regress_schema_3 CASCADE;
 DROP SCHEMA regress_schema_4 CASCADE;
 DROP SCHEMA regress_schema_5 CASCADE;
+DROP SCHEMA regress_schema_6 CASCADE;
+DROP SCHEMA regress_schema_7 CASCADE;
 
 -- Clean up
 DROP ROLE regress_create_schema_role;
diff --git a/src/test/regress/sql/create_type.sql b/src/test/regress/sql/create_type.sql
index c25018029c2..41b6f9a74ec 100644
--- a/src/test/regress/sql/create_type.sql
+++ b/src/test/regress/sql/create_type.sql
@@ -32,6 +32,18 @@ CREATE FUNCTION int44out(city_budget)
    AS :'regresslib'
    LANGUAGE C STRICT IMMUTABLE;
 
+----- CREATE SCHEMA CREATE TYPE test
+--error. cannot CREATE TYPE to other schema
+CREATE SCHEMA regress_schema_1
+   CREATE TYPE public.widget (
+      internallength = 24,
+      input = widget_in,
+      output = widget_out,
+      typmod_in = numerictypmodin,
+      typmod_out = numerictypmodout,
+      alignment = double
+   );
+
 CREATE TYPE widget (
    internallength = 24,
    input = widget_in,
-- 
2.34.1



  [text/x-patch] v7-0001-Don-t-try-to-re-order-the-subcommands-of-CREATE-SCHEMA.patch (26.3K, 5-v7-0001-Don-t-try-to-re-order-the-subcommands-of-CREATE-SCHEMA.patch)
  download | inline diff:
From c2b34a1ebc8e4af4a9e411d83b5e629154ebe043 Mon Sep 17 00:00:00 2001
From: Tom Lane <[email protected]>
Date: Tue, 19 Aug 2025 10:52:05 +0800
Subject: [PATCH v7 1/4] Don't try to re-order the subcommands of CREATE SCHEMA
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

transformCreateSchemaStmtElements has always believed that it is
supposed to re-order the subcommands of CREATE SCHEMA into a safe
execution order.  However, it is nowhere near being capable of doing
that correctly.  Nor is there reason to think that it ever will be,
or that that is a well-defined requirement, or that there's any basis
in the SQL standard for it.  Moreover, the problem will get worse as
we add more subcommand types.  Let's just drop the whole idea and
execute the commands in the order given, which seems like a much less
astonishment-prone definition anyway.

Along the way, pass down a ParseState so that we can provide an
error cursor for the "wrong schema name" error, and fix
transformCreateSchemaStmtElements so that it doesn't scribble
on the parsetree passed to it.

Note: This will cause compatibility issue,
for example:
CREATE SCHEMA regress_schema_2
  CREATE VIEW abcd_view AS SELECT a FROM abcd
  CREATE TABLE abcd (a int);

With the patch, it will throw an error, whereas on HEAD it won’t.

Discussion: https://postgr.es/m/[email protected]
---
 doc/src/sgml/ref/create_schema.sgml         |  10 +-
 src/backend/commands/extension.c            |   7 +-
 src/backend/commands/schemacmds.c           |  15 ++-
 src/backend/parser/parse_utilcmd.c          | 135 ++++++++------------
 src/backend/tcop/utility.c                  |   3 +-
 src/include/commands/schemacmds.h           |   7 +-
 src/include/parser/parse_utilcmd.h          |   3 +-
 src/test/regress/expected/create_schema.out |  37 ++++++
 src/test/regress/expected/create_view.out   |   2 +
 src/test/regress/expected/event_trigger.out |   2 +-
 src/test/regress/expected/namespace.out     |   9 +-
 src/test/regress/sql/create_schema.sql      |   5 +
 src/test/regress/sql/namespace.sql          |  11 +-
 src/tools/pgindent/typedefs.list            |   1 -
 14 files changed, 132 insertions(+), 115 deletions(-)

diff --git a/doc/src/sgml/ref/create_schema.sgml b/doc/src/sgml/ref/create_schema.sgml
index ed69298ccc6..625793a6b67 100644
--- a/doc/src/sgml/ref/create_schema.sgml
+++ b/doc/src/sgml/ref/create_schema.sgml
@@ -193,12 +193,10 @@ CREATE VIEW hollywood.winners AS
   </para>
 
   <para>
-   The SQL standard specifies that the subcommands in <command>CREATE
-   SCHEMA</command> can appear in any order.  The present
-   <productname>PostgreSQL</productname> implementation does not
-   handle all cases of forward references in subcommands; it might
-   sometimes be necessary to reorder the subcommands in order to avoid
-   forward references.
+   <productname>PostgreSQL</productname> executes the subcommands
+   in <command>CREATE SCHEMA</command> in the order given.  Other
+   implementations may try to rearrange the subcommands into dependency
+   order, but that is hard if not impossible to do correctly.
   </para>
 
   <para>
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index e6f9ab6dfd6..ad578bc76f7 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1773,14 +1773,17 @@ CreateExtensionInternal(char *extensionName,
 
 		if (!OidIsValid(schemaOid))
 		{
+			ParseState *pstate = make_parsestate(NULL);
 			CreateSchemaStmt *csstmt = makeNode(CreateSchemaStmt);
 
+			pstate->p_sourcetext = "(generated CREATE SCHEMA command)";
+
 			csstmt->schemaname = schemaName;
 			csstmt->authrole = NULL;	/* will be created by current user */
 			csstmt->schemaElts = NIL;
 			csstmt->if_not_exists = false;
-			CreateSchemaCommand(csstmt, "(generated CREATE SCHEMA command)",
-								-1, -1);
+
+			CreateSchemaCommand(pstate, csstmt, -1, -1);
 
 			/*
 			 * CreateSchemaCommand includes CommandCounterIncrement, so new
diff --git a/src/backend/commands/schemacmds.c b/src/backend/commands/schemacmds.c
index 0f03d9743d2..b32935215f9 100644
--- a/src/backend/commands/schemacmds.c
+++ b/src/backend/commands/schemacmds.c
@@ -49,7 +49,7 @@ static void AlterSchemaOwner_internal(HeapTuple tup, Relation rel, Oid newOwnerI
  * a subquery.
  */
 Oid
-CreateSchemaCommand(CreateSchemaStmt *stmt, const char *queryString,
+CreateSchemaCommand(ParseState *pstate, CreateSchemaStmt *stmt,
 					int stmt_location, int stmt_len)
 {
 	const char *schemaName = stmt->schemaname;
@@ -189,12 +189,13 @@ CreateSchemaCommand(CreateSchemaStmt *stmt, const char *queryString,
 
 	/*
 	 * Examine the list of commands embedded in the CREATE SCHEMA command, and
-	 * reorganize them into a sequentially executable order with no forward
-	 * references.  Note that the result is still a list of raw parsetrees ---
-	 * we cannot, in general, run parse analysis on one statement until we
-	 * have actually executed the prior ones.
+	 * do preliminary transformations (mostly, verify that none are trying to
+	 * create objects outside the new schema).  Note that the result is still
+	 * a list of raw parsetrees --- we cannot, in general, run parse analysis
+	 * on one statement until we have actually executed the prior ones.
 	 */
-	parsetree_list = transformCreateSchemaStmtElements(stmt->schemaElts,
+	parsetree_list = transformCreateSchemaStmtElements(pstate,
+													   stmt->schemaElts,
 													   schemaName);
 
 	/*
@@ -219,7 +220,7 @@ CreateSchemaCommand(CreateSchemaStmt *stmt, const char *queryString,
 
 		/* do this step */
 		ProcessUtility(wrapper,
-					   queryString,
+					   pstate->p_sourcetext,
 					   false,
 					   PROCESS_UTILITY_SUBCOMMAND,
 					   NULL,
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..74672a458d5 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -95,18 +95,6 @@ typedef struct
 	bool		ofType;			/* true if statement contains OF typename */
 } CreateStmtContext;
 
-/* State shared by transformCreateSchemaStmtElements and its subroutines */
-typedef struct
-{
-	const char *schemaname;		/* name of schema */
-	List	   *sequences;		/* CREATE SEQUENCE items */
-	List	   *tables;			/* CREATE TABLE items */
-	List	   *views;			/* CREATE VIEW items */
-	List	   *indexes;		/* CREATE INDEX items */
-	List	   *triggers;		/* CREATE TRIGGER items */
-	List	   *grants;			/* GRANT items */
-} CreateSchemaStmtContext;
-
 
 static void transformColumnDefinition(CreateStmtContext *cxt,
 									  ColumnDef *column);
@@ -133,7 +121,8 @@ static void transformCheckConstraints(CreateStmtContext *cxt,
 static void transformConstraintAttrs(CreateStmtContext *cxt,
 									 List *constraintList);
 static void transformColumnType(CreateStmtContext *cxt, ColumnDef *column);
-static void setSchemaName(const char *context_schema, char **stmt_schema_name);
+static void checkSchemaName(ParseState *pstate, const char *context_schema,
+							RangeVar *relation);
 static void transformPartitionCmd(CreateStmtContext *cxt, PartitionCmd *cmd);
 static List *transformPartitionRangeBounds(ParseState *pstate, List *blist,
 										   Relation parent);
@@ -4076,51 +4065,35 @@ transformColumnType(CreateStmtContext *cxt, ColumnDef *column)
  * transformCreateSchemaStmtElements -
  *	  analyzes the elements of a CREATE SCHEMA statement
  *
- * Split the schema element list from a CREATE SCHEMA statement into
- * individual commands and place them in the result list in an order
- * such that there are no forward references (e.g. GRANT to a table
- * created later in the list). Note that the logic we use for determining
- * forward references is presently quite incomplete.
+ * 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.
+ * 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.
  *
  * "schemaName" is the name of the schema that will be used for the creation
- * of the objects listed, that may be compiled from the schema name defined
+ * of the objects listed.  It may be obtained from the schema name defined
  * in the statement or a role specification.
  *
- * SQL also allows constraints to make forward references, so thumb through
- * the table columns and move forward references to a posterior alter-table
- * command.
- *
  * 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: this breaks the rules a little bit by modifying schema-name fields
- * within passed-in structs.  However, the transformation would be the same
- * if done over, so it should be all right to scribble on the input to this
- * extent.
  */
 List *
-transformCreateSchemaStmtElements(List *schemaElts, const char *schemaName)
+transformCreateSchemaStmtElements(ParseState *pstate, List *schemaElts,
+								  const char *schemaName)
 {
-	CreateSchemaStmtContext cxt;
-	List	   *result;
-	ListCell   *elements;
-
-	cxt.schemaname = schemaName;
-	cxt.sequences = NIL;
-	cxt.tables = NIL;
-	cxt.views = NIL;
-	cxt.indexes = NIL;
-	cxt.triggers = NIL;
-	cxt.grants = NIL;
+	List	   *elements = NIL;
+	ListCell   *lc;
 
 	/*
-	 * Run through each schema element in the schema element list. Separate
-	 * statements by type, and do preliminary analysis.
+	 * Run through each schema element in the schema element list.  Check
+	 * target schema names, and collect the list of actions to be done.
 	 */
-	foreach(elements, schemaElts)
+	foreach(lc, schemaElts)
 	{
-		Node	   *element = lfirst(elements);
+		Node	   *element = lfirst(lc);
 
 		switch (nodeTag(element))
 		{
@@ -4128,8 +4101,8 @@ transformCreateSchemaStmtElements(List *schemaElts, const char *schemaName)
 				{
 					CreateSeqStmt *elp = (CreateSeqStmt *) element;
 
-					setSchemaName(cxt.schemaname, &elp->sequence->schemaname);
-					cxt.sequences = lappend(cxt.sequences, element);
+					checkSchemaName(pstate, schemaName, elp->sequence);
+					elements = lappend(elements, element);
 				}
 				break;
 
@@ -4137,12 +4110,8 @@ transformCreateSchemaStmtElements(List *schemaElts, const char *schemaName)
 				{
 					CreateStmt *elp = (CreateStmt *) element;
 
-					setSchemaName(cxt.schemaname, &elp->relation->schemaname);
-
-					/*
-					 * XXX todo: deal with constraints
-					 */
-					cxt.tables = lappend(cxt.tables, element);
+					checkSchemaName(pstate, schemaName, elp->relation);
+					elements = lappend(elements, element);
 				}
 				break;
 
@@ -4150,12 +4119,8 @@ transformCreateSchemaStmtElements(List *schemaElts, const char *schemaName)
 				{
 					ViewStmt   *elp = (ViewStmt *) element;
 
-					setSchemaName(cxt.schemaname, &elp->view->schemaname);
-
-					/*
-					 * XXX todo: deal with references between views
-					 */
-					cxt.views = lappend(cxt.views, element);
+					checkSchemaName(pstate, schemaName, elp->view);
+					elements = lappend(elements, element);
 				}
 				break;
 
@@ -4163,8 +4128,8 @@ transformCreateSchemaStmtElements(List *schemaElts, const char *schemaName)
 				{
 					IndexStmt  *elp = (IndexStmt *) element;
 
-					setSchemaName(cxt.schemaname, &elp->relation->schemaname);
-					cxt.indexes = lappend(cxt.indexes, element);
+					checkSchemaName(pstate, schemaName, elp->relation);
+					elements = lappend(elements, element);
 				}
 				break;
 
@@ -4172,13 +4137,13 @@ transformCreateSchemaStmtElements(List *schemaElts, const char *schemaName)
 				{
 					CreateTrigStmt *elp = (CreateTrigStmt *) element;
 
-					setSchemaName(cxt.schemaname, &elp->relation->schemaname);
-					cxt.triggers = lappend(cxt.triggers, element);
+					checkSchemaName(pstate, schemaName, elp->relation);
+					elements = lappend(elements, element);
 				}
 				break;
 
 			case T_GrantStmt:
-				cxt.grants = lappend(cxt.grants, element);
+				elements = lappend(elements, element);
 				break;
 
 			default:
@@ -4187,32 +4152,40 @@ transformCreateSchemaStmtElements(List *schemaElts, const char *schemaName)
 		}
 	}
 
-	result = NIL;
-	result = list_concat(result, cxt.sequences);
-	result = list_concat(result, cxt.tables);
-	result = list_concat(result, cxt.views);
-	result = list_concat(result, cxt.indexes);
-	result = list_concat(result, cxt.triggers);
-	result = list_concat(result, cxt.grants);
-
-	return result;
+	return elements;
 }
 
 /*
- * setSchemaName
- *		Set or check schema name in an element of a CREATE SCHEMA command
+ * checkSchemaName
+ *		Check schema name in an element of a CREATE SCHEMA command
+ *
+ * It's okay if the command doesn't specify a target schema name, because
+ * CreateSchemaCommand will set up the default creation schema to be the
+ * new schema.  But if a target schema name is given, it had better match.
+ * We also have to check that the command doesn't say CREATE TEMP, since
+ * that would likewise put the object into the wrong schema.
  */
 static void
-setSchemaName(const char *context_schema, char **stmt_schema_name)
+checkSchemaName(ParseState *pstate, const char *context_schema,
+				RangeVar *relation)
 {
-	if (*stmt_schema_name == NULL)
-		*stmt_schema_name = unconstify(char *, context_schema);
-	else if (strcmp(context_schema, *stmt_schema_name) != 0)
+	if (relation->schemaname != NULL &&
+		strcmp(context_schema, relation->schemaname) != 0)
 		ereport(ERROR,
-				(errcode(ERRCODE_INVALID_SCHEMA_DEFINITION),
-				 errmsg("CREATE specifies a schema (%s) "
+				errcode(ERRCODE_INVALID_SCHEMA_DEFINITION),
+				errmsg("CREATE specifies a schema (%s) "
 						"different from the one being created (%s)",
-						*stmt_schema_name, context_schema)));
+						relation->schemaname, context_schema),
+				parser_errposition(pstate, relation->location));
+
+	if (relation->relpersistence == RELPERSISTENCE_TEMP)
+	{
+		/* spell this error the same as in RangeVarAdjustRelationPersistence */
+		ereport(ERROR,
+				errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+				errmsg("cannot create temporary relation in non-temporary schema"),
+				parser_errposition(pstate, relation->location));
+	}
 }
 
 /*
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..ec2bbe5587c 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1111,8 +1111,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				 * relation and attribute manipulation
 				 */
 			case T_CreateSchemaStmt:
-				CreateSchemaCommand((CreateSchemaStmt *) parsetree,
-									queryString,
+				CreateSchemaCommand(pstate, (CreateSchemaStmt *) parsetree,
 									pstmt->stmt_location,
 									pstmt->stmt_len);
 
diff --git a/src/include/commands/schemacmds.h b/src/include/commands/schemacmds.h
index 8557176b66a..835265bb67c 100644
--- a/src/include/commands/schemacmds.h
+++ b/src/include/commands/schemacmds.h
@@ -16,12 +16,9 @@
 #define SCHEMACMDS_H
 
 #include "catalog/objectaddress.h"
-#include "nodes/parsenodes.h"
-
-extern Oid	CreateSchemaCommand(CreateSchemaStmt *stmt,
-								const char *queryString,
-								int stmt_location, int stmt_len);
+#include "parser/parse_node.h"
 
+extern Oid	CreateSchemaCommand(ParseState *pstate, CreateSchemaStmt *stmt, int stmt_location, int stmt_len);
 extern ObjectAddress RenameSchema(const char *oldname, const char *newname);
 extern ObjectAddress AlterSchemaOwner(const char *name, Oid newOwnerId);
 extern void AlterSchemaOwner_oid(Oid schemaoid, Oid newOwnerId);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..da514198ced 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -30,7 +30,8 @@ extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-extern List *transformCreateSchemaStmtElements(List *schemaElts,
+extern List *transformCreateSchemaStmtElements(ParseState *pstate,
+											   List *schemaElts,
 											   const char *schemaName);
 extern PartitionBoundSpec *transformPartitionBound(ParseState *pstate, Relation parent,
 												   PartitionBoundSpec *spec);
diff --git a/src/test/regress/expected/create_schema.out b/src/test/regress/expected/create_schema.out
index 93302a07efc..38530c282a9 100644
--- a/src/test/regress/expected/create_schema.out
+++ b/src/test/regress/expected/create_schema.out
@@ -9,55 +9,92 @@ CREATE ROLE regress_create_schema_role SUPERUSER;
 CREATE SCHEMA AUTHORIZATION regress_create_schema_role
   CREATE SEQUENCE schema_not_existing.seq;
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_create_schema_role)
+LINE 2:   CREATE SEQUENCE schema_not_existing.seq;
+                          ^
 CREATE SCHEMA AUTHORIZATION regress_create_schema_role
   CREATE TABLE schema_not_existing.tab (id int);
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_create_schema_role)
+LINE 2:   CREATE TABLE schema_not_existing.tab (id int);
+                       ^
 CREATE SCHEMA AUTHORIZATION regress_create_schema_role
   CREATE VIEW schema_not_existing.view AS SELECT 1;
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_create_schema_role)
+LINE 2:   CREATE VIEW schema_not_existing.view AS SELECT 1;
+                      ^
 CREATE SCHEMA AUTHORIZATION regress_create_schema_role
   CREATE INDEX ON schema_not_existing.tab (id);
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_create_schema_role)
+LINE 2:   CREATE INDEX ON schema_not_existing.tab (id);
+                          ^
 CREATE SCHEMA AUTHORIZATION regress_create_schema_role
   CREATE TRIGGER schema_trig BEFORE INSERT ON schema_not_existing.tab
   EXECUTE FUNCTION schema_trig.no_func();
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_create_schema_role)
+LINE 2:   CREATE TRIGGER schema_trig BEFORE INSERT ON schema_not_exi...
+                                                      ^
 -- Again, with a role specification and no schema names.
 SET ROLE regress_create_schema_role;
 CREATE SCHEMA AUTHORIZATION CURRENT_ROLE
   CREATE SEQUENCE schema_not_existing.seq;
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_create_schema_role)
+LINE 2:   CREATE SEQUENCE schema_not_existing.seq;
+                          ^
 CREATE SCHEMA AUTHORIZATION CURRENT_ROLE
   CREATE TABLE schema_not_existing.tab (id int);
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_create_schema_role)
+LINE 2:   CREATE TABLE schema_not_existing.tab (id int);
+                       ^
 CREATE SCHEMA AUTHORIZATION CURRENT_ROLE
   CREATE VIEW schema_not_existing.view AS SELECT 1;
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_create_schema_role)
+LINE 2:   CREATE VIEW schema_not_existing.view AS SELECT 1;
+                      ^
 CREATE SCHEMA AUTHORIZATION CURRENT_ROLE
   CREATE INDEX ON schema_not_existing.tab (id);
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_create_schema_role)
+LINE 2:   CREATE INDEX ON schema_not_existing.tab (id);
+                          ^
 CREATE SCHEMA AUTHORIZATION CURRENT_ROLE
   CREATE TRIGGER schema_trig BEFORE INSERT ON schema_not_existing.tab
   EXECUTE FUNCTION schema_trig.no_func();
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_create_schema_role)
+LINE 2:   CREATE TRIGGER schema_trig BEFORE INSERT ON schema_not_exi...
+                                                      ^
 -- Again, with a schema name and a role specification.
 CREATE SCHEMA regress_schema_1 AUTHORIZATION CURRENT_ROLE
   CREATE SEQUENCE schema_not_existing.seq;
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_schema_1)
+LINE 2:   CREATE SEQUENCE schema_not_existing.seq;
+                          ^
 CREATE SCHEMA regress_schema_1 AUTHORIZATION CURRENT_ROLE
   CREATE TABLE schema_not_existing.tab (id int);
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_schema_1)
+LINE 2:   CREATE TABLE schema_not_existing.tab (id int);
+                       ^
 CREATE SCHEMA regress_schema_1 AUTHORIZATION CURRENT_ROLE
   CREATE VIEW schema_not_existing.view AS SELECT 1;
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_schema_1)
+LINE 2:   CREATE VIEW schema_not_existing.view AS SELECT 1;
+                      ^
 CREATE SCHEMA regress_schema_1 AUTHORIZATION CURRENT_ROLE
   CREATE INDEX ON schema_not_existing.tab (id);
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_schema_1)
+LINE 2:   CREATE INDEX ON schema_not_existing.tab (id);
+                          ^
 CREATE SCHEMA regress_schema_1 AUTHORIZATION CURRENT_ROLE
   CREATE TRIGGER schema_trig BEFORE INSERT ON schema_not_existing.tab
   EXECUTE FUNCTION schema_trig.no_func();
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_schema_1)
+LINE 2:   CREATE TRIGGER schema_trig BEFORE INSERT ON schema_not_exi...
+                                                      ^
 RESET ROLE;
+--error. Forward reference is not allowed.
+CREATE SCHEMA regress_schema_2
+  CREATE VIEW abcd_view AS SELECT a FROM abcd
+  CREATE TABLE abcd (a int);
+ERROR:  relation "abcd" does not exist
+LINE 2:   CREATE VIEW abcd_view AS SELECT a FROM abcd
+                                                 ^
 -- Cases where the schema creation succeeds.
 -- The schema created matches the role name.
 CREATE SCHEMA AUTHORIZATION regress_create_schema_role
diff --git a/src/test/regress/expected/create_view.out b/src/test/regress/expected/create_view.out
index f551624afb3..b305ceea033 100644
--- a/src/test/regress/expected/create_view.out
+++ b/src/test/regress/expected/create_view.out
@@ -126,6 +126,8 @@ ERROR:  cannot create temporary relation in non-temporary schema
 CREATE SCHEMA test_view_schema
     CREATE TEMP VIEW testview AS SELECT 1;
 ERROR:  cannot create temporary relation in non-temporary schema
+LINE 2:     CREATE TEMP VIEW testview AS SELECT 1;
+                             ^
 -- joins: if any of the join relations are temporary, the view
 -- should also be temporary
 -- should be non-temp
diff --git a/src/test/regress/expected/event_trigger.out b/src/test/regress/expected/event_trigger.out
index 7b2198eac6f..bafde8706fe 100644
--- a/src/test/regress/expected/event_trigger.out
+++ b/src/test/regress/expected/event_trigger.out
@@ -411,12 +411,12 @@ NOTICE:  END: command_tag=CREATE TABLE type=table identity=evttrig.one
 NOTICE:  END: command_tag=CREATE INDEX type=index identity=evttrig.one_pkey
 NOTICE:  END: command_tag=ALTER SEQUENCE type=sequence identity=evttrig.one_col_a_seq
 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 INDEX type=index identity=evttrig.one_idx
 -- Partitioned tables with a partitioned index
 CREATE TABLE evttrig.parted (
     id int PRIMARY KEY)
diff --git a/src/test/regress/expected/namespace.out b/src/test/regress/expected/namespace.out
index dbbda72d395..2e582e783c2 100644
--- a/src/test/regress/expected/namespace.out
+++ b/src/test/regress/expected/namespace.out
@@ -10,13 +10,14 @@ SELECT pg_catalog.set_config('search_path', ' ', false);
 (1 row)
 
 CREATE SCHEMA test_ns_schema_1
+       CREATE TABLE abc (
+              a serial,
+              b int UNIQUE
+       )
        CREATE UNIQUE INDEX abc_a_idx ON abc (a)
        CREATE VIEW abc_view AS
               SELECT a+1 AS a, b+1 AS b FROM abc
-       CREATE TABLE abc (
-              a serial,
-              b int UNIQUE
-       );
+;
 -- verify that the correct search_path restored on abort
 SET search_path to public;
 BEGIN;
diff --git a/src/test/regress/sql/create_schema.sql b/src/test/regress/sql/create_schema.sql
index 1b7064247a1..b3dc1cfd758 100644
--- a/src/test/regress/sql/create_schema.sql
+++ b/src/test/regress/sql/create_schema.sql
@@ -47,6 +47,11 @@ CREATE SCHEMA regress_schema_1 AUTHORIZATION CURRENT_ROLE
   EXECUTE FUNCTION schema_trig.no_func();
 RESET ROLE;
 
+--error. Forward reference is not allowed.
+CREATE SCHEMA regress_schema_2
+  CREATE VIEW abcd_view AS SELECT a FROM abcd
+  CREATE TABLE abcd (a int);
+
 -- Cases where the schema creation succeeds.
 -- The schema created matches the role name.
 CREATE SCHEMA AUTHORIZATION regress_create_schema_role
diff --git a/src/test/regress/sql/namespace.sql b/src/test/regress/sql/namespace.sql
index 306cdc2d8c6..a75d4f580d3 100644
--- a/src/test/regress/sql/namespace.sql
+++ b/src/test/regress/sql/namespace.sql
@@ -7,15 +7,16 @@
 SELECT pg_catalog.set_config('search_path', ' ', false);
 
 CREATE SCHEMA test_ns_schema_1
+       CREATE TABLE abc (
+              a serial,
+              b int UNIQUE
+       )
+
        CREATE UNIQUE INDEX abc_a_idx ON abc (a)
 
        CREATE VIEW abc_view AS
               SELECT a+1 AS a, b+1 AS b FROM abc
-
-       CREATE TABLE abc (
-              a serial,
-              b int UNIQUE
-       );
+;
 
 -- verify that the correct search_path restored on abort
 SET search_path to public;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index a13e8162890..b89c3269a8d 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -558,7 +558,6 @@ CreateRangeStmt
 CreateReplicationSlotCmd
 CreateRoleStmt
 CreateSchemaStmt
-CreateSchemaStmtContext
 CreateSeqStmt
 CreateStatsStmt
 CreateStmt
-- 
2.34.1



^ permalink  raw  reply  [nested|flat] 32+ messages in thread

* Re: CREATE SCHEMA ... CREATE DOMAIN support
  2024-11-30 19:08 Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  2024-12-03 19:35 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Peter Eisentraut <[email protected]>
  2024-12-03 20:07   ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  2024-12-11 06:22     ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <[email protected]>
  2024-12-11 17:08       ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  2025-08-19 03:36         ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <[email protected]>
  2025-08-22 08:59           ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <[email protected]>
@ 2025-09-02 21:24             ` Tom Lane <[email protected]>
  2025-12-17 13:29               ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <[email protected]>
  1 sibling, 1 reply; 32+ messages in thread

From: Tom Lane @ 2025-09-02 21:24 UTC (permalink / raw)
  To: jian he <[email protected]>; +Cc: Kirill Reshke <[email protected]>; Peter Eisentraut <[email protected]>; PostgreSQL Hackers <[email protected]>

jian he <[email protected]> writes:
> Please check the latest attached.
> v7-0001-Don-t-try-to-re-order-the-subcommands-of-CREATE-SCHEMA.patch
> v7-0002-CREATE-SCHEMA-CREATE-DOMAIN.patch
> v7-0003-CREATE-SCHEMA-CREATE-COLLATION.patch
> v7-0004-CREATE-SCHEMA-CREATE-TYPE.patch

I think this is still kind of blocked, because it's not clear to me
whether we have consensus about it being okay to do 0001.

Re-reading the thread, the only real use-case for re-ordering that
anyone proposed is that foreign key references should be able to be
forward references to tables created later in the same CREATE SCHEMA.
I concede first that this is a somewhat-plausible use-case and
second that it is pretty clearly required by spec.  The fact remains
however that we have never supported that in two dozen years, and
the number of complaints about the omission could be counted without
running out of thumbs.  So, how about the following plan of action?

1. Rip out subcommand re-ordering as currently implemented, and do the
subcommands in the given order.

2. When a CREATE TABLE subcommand includes a FOREIGN KEY clause,
transform that clause into ALTER TABLE ADD FOREIGN KEY, and push
it to the back of the CREATE SCHEMA's to-do list.

#2 gives us at least pro-forma spec compliance, and AFAICS it does
not introduce any command re-ordering bugs.  Foreign key clauses
don't depend on each other, so shoving them to the end without any
further sorting should be fine.

Also ... we don't really have to do #2 until someone complains about
the lack of ability to do forward references, which going by history
is probably not going to be soon.  I certainly don't feel that it has
to be completed in this patchset.

			regards, tom lane





^ permalink  raw  reply  [nested|flat] 32+ messages in thread

* Re: CREATE SCHEMA ... CREATE DOMAIN support
  2024-11-30 19:08 Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  2024-12-03 19:35 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Peter Eisentraut <[email protected]>
  2024-12-03 20:07   ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  2024-12-11 06:22     ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <[email protected]>
  2024-12-11 17:08       ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  2025-08-19 03:36         ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <[email protected]>
  2025-08-22 08:59           ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <[email protected]>
  2025-09-02 21:24             ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
@ 2025-12-17 13:29               ` jian he <[email protected]>
  2025-12-31 01:40                 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <[email protected]>
  2026-04-03 18:35                 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  0 siblings, 2 replies; 32+ messages in thread

From: jian he @ 2025-12-17 13:29 UTC (permalink / raw)
  To: Tom Lane <[email protected]>; +Cc: Kirill Reshke <[email protected]>; Peter Eisentraut <[email protected]>; PostgreSQL Hackers <[email protected]>

On Wed, Sep 3, 2025 at 5:24 AM Tom Lane <[email protected]> wrote:
>

> jian he <[email protected]> writes:
> > Please check the latest attached.
> > v7-0001-Don-t-try-to-re-order-the-subcommands-of-CREATE-SCHEMA.patch

> I think this is still kind of blocked, because it's not clear to me
> whether we have consensus about it being okay to do 0001.
>
> Re-reading the thread, the only real use-case for re-ordering that
> anyone proposed is that foreign key references should be able to be
> forward references to tables created later in the same CREATE SCHEMA.
> I concede first that this is a somewhat-plausible use-case and
> second that it is pretty clearly required by spec.  The fact remains
> however that we have never supported that in two dozen years, and
> the number of complaints about the omission could be counted without
> running out of thumbs.  So, how about the following plan of action?
>
> 1. Rip out subcommand re-ordering as currently implemented, and do the
> subcommands in the given order.
>
> 2. When a CREATE TABLE subcommand includes a FOREIGN KEY clause,
> transform that clause into ALTER TABLE ADD FOREIGN KEY, and push
> it to the back of the CREATE SCHEMA's to-do list.
>
> #2 gives us at least pro-forma spec compliance, and AFAICS it does
> not introduce any command re-ordering bugs.  Foreign key clauses
> don't depend on each other, so shoving them to the end without any
> further sorting should be fine.
>

Hi.

I just want to confirm we are on the same page. so you expect CREATE SCHEMA
OptSchemaEltList executed as the specified order given, also expect cross table
FOREIGN KEY referencing works just fine too?

If so, obviously It will be a hack, but it seems the hack is easier to
understand.

1. For CREATE TABLE, some case, we wrap the FK Constraint node in the form of an
ALTER TABLE ADD CONSTRAINT command, creating the FK constraint only after the
relation itself has been created. see transformFKConstraints comments.  For
CREATE SCHEMA, we can make it after multiple relations are created, then process
FK constraint too.

2. src/backend/parser/gram.y already processes most of the information for FK.
transformFKConstraints merely wraps the FK Constraint node into an
AlterTableCmd, which is then wrapped into an AlterTableStmt.

In the HEAD, CREATE SCHEMA will fail when you first mention FK then PK.
but with the attached, it will work fine.
for example, below two will error out in the HEAD, but it will works
fine with the attached
patch.

CREATE SCHEMA s2
  CREATE TABLE t2(a int, b int, c int, foreign key (a, b) references
t1 match full) partition by range (a, b, c)
  CREATE TABLE t1(a int, b int, primary key(b, a)) partition by range (a, b);

CREATE SCHEMA s2
  CREATE TABLE t2(a int, b int, c int, foreign key (a, b) references
t1 match full) partition by range (a, b, c)
  CREATE TABLE t1(a int, b int, primary key(b, a)) partition by range (a, b);



--
jian
https://www.enterprisedb.com/


Attachments:

  [application/octet-stream] v8-0002-make-create-foreign-key-executed-at-the-end-of-CREATE-SCHEMA.nocfbot (3.2K, 2-v8-0002-make-create-foreign-key-executed-at-the-end-of-CREATE-SCHEMA.nocfbot)
  download

  [application/octet-stream] v8-0001-Don-t-try-to-re-order-the-subcommands-of-CREATE-SCHEMA.nocfbot (26.3K, 3-v8-0001-Don-t-try-to-re-order-the-subcommands-of-CREATE-SCHEMA.nocfbot)
  download

^ permalink  raw  reply  [nested|flat] 32+ messages in thread

* Re: CREATE SCHEMA ... CREATE DOMAIN support
  2024-11-30 19:08 Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  2024-12-03 19:35 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Peter Eisentraut <[email protected]>
  2024-12-03 20:07   ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  2024-12-11 06:22     ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <[email protected]>
  2024-12-11 17:08       ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  2025-08-19 03:36         ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <[email protected]>
  2025-08-22 08:59           ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <[email protected]>
  2025-09-02 21:24             ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  2025-12-17 13:29               ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <[email protected]>
@ 2025-12-31 01:40                 ` jian he <[email protected]>
  2026-01-19 15:06                   ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <[email protected]>
  1 sibling, 1 reply; 32+ messages in thread

From: jian he @ 2025-12-31 01:40 UTC (permalink / raw)
  To: Tom Lane <[email protected]>; +Cc: Kirill Reshke <[email protected]>; Peter Eisentraut <[email protected]>; PostgreSQL Hackers <[email protected]>

> On Wed, Sep 3, 2025 at 5:24 AM Tom Lane <[email protected]> wrote:
> >

>
> > I think this is still kind of blocked, because it's not clear to me
> > whether we have consensus about it being okay to do 0001.
> >
> > Re-reading the thread, the only real use-case for re-ordering that
> > anyone proposed is that foreign key references should be able to be
> > forward references to tables created later in the same CREATE SCHEMA.
> > I concede first that this is a somewhat-plausible use-case and
> > second that it is pretty clearly required by spec.  The fact remains
> > however that we have never supported that in two dozen years, and
> > the number of complaints about the omission could be counted without
> > running out of thumbs.  So, how about the following plan of action?
> >
> > 1. Rip out subcommand re-ordering as currently implemented, and do the
> > subcommands in the given order.
> >
> > 2. When a CREATE TABLE subcommand includes a FOREIGN KEY clause,
> > transform that clause into ALTER TABLE ADD FOREIGN KEY, and push
> > it to the back of the CREATE SCHEMA's to-do list.
> >
> > #2 gives us at least pro-forma spec compliance, and AFAICS it does
> > not introduce any command re-ordering bugs.  Foreign key clauses
> > don't depend on each other, so shoving them to the end without any
> > further sorting should be fine.
> >

hi.

v8-0001 through v8-0004 are rebased from v7 with some adjustments.

v8-0005 transforms foreign key constraints into ALTER TABLE ... ADD FOREIGN KEY.
This works for both column constraint and table constraint. Below is a contrived
complex test case I came up with to verify correctness.

CREATE SCHEMA regress_schema_8
CREATE TABLE regress_schema_8.t2 (
    b int,
    a int REFERENCES t1 DEFERRABLE INITIALLY DEFERRED NOT ENFORCED
        REFERENCES t3 DEFERRABLE INITIALLY DEFERRED NOT ENFORCED,
    CONSTRAINT fk FOREIGN KEY (a) REFERENCES t1 DEFERRABLE INITIALLY
DEFERRED NOT ENFORCED)
CREATE TABLE regress_schema_8.t1 (a int PRIMARY KEY)
CREATE TABLE t3 (a int PRIMARY KEY)
CREATE TABLE t4(b int,
                a int REFERENCES t5 DEFERRABLE INITIALLY DEFERRED NOT ENFORCED
                    REFERENCES t6 DEFERRABLE INITIALLY DEFERRED NOT ENFORCED,
    CONSTRAINT fk FOREIGN KEY (a) REFERENCES t6 DEFERRABLE INITIALLY
DEFERRED NOT ENFORCED)
CREATE TABLE t5 (a int, b int, PRIMARY KEY (a))
CREATE TABLE t6 (a int, b int, PRIMARY KEY (a));


Does this resolve your #2?


--
jian
https://www.enterprisedb.com/


Attachments:

  [text/x-patch] v8-0003-CREATE-SCHEMA-CREATE-COLLATION.patch (13.9K, 2-v8-0003-CREATE-SCHEMA-CREATE-COLLATION.patch)
  download | inline diff:
From f585d3966cedf6046a863a7a9018a3ffd8399559 Mon Sep 17 00:00:00 2001
From: jian he <[email protected]>
Date: Tue, 30 Dec 2025 13:40:48 +0800
Subject: [PATCH v8 3/5] CREATE SCHEMA CREATE COLLATION

The SQL standard allows collation to be specified in a CREATE SCHEMA statement.
This adds support for that capability.

For example:
    CREATE SCHEMA schema_name CREATE COLLATION coll_icu_und FROM "und-x-icu";

The collation will be created within the to be created schema.  The collation
name can be schema-qualified or database-qualified, however it's not allowed to
let collation create within a different schema.

Discussion: https://postgr.es/m/CALdSSPh4jUSDsWu3K58hjO60wnTRR0DuO4CKRcwa8EVuOSfXxg@mail.gmail.com
---
 doc/src/sgml/ref/create_schema.sgml           |  3 +-
 src/backend/catalog/objectaddress.c           | 18 ++++++++
 src/backend/parser/gram.y                     |  1 +
 src/backend/parser/parse_utilcmd.c            | 40 +++++++++++++++++
 src/include/catalog/objectaddress.h           |  1 +
 .../expected/create_schema.out                |  4 +-
 .../test_ddl_deparse/sql/create_schema.sql    |  3 +-
 .../regress/expected/collate.icu.utf8.out     | 15 +++++++
 src/test/regress/expected/create_schema.out   | 43 +++++++++++++++++++
 src/test/regress/sql/collate.icu.utf8.sql     |  7 +++
 src/test/regress/sql/create_schema.sql        | 29 +++++++++++++
 11 files changed, 161 insertions(+), 3 deletions(-)

diff --git a/doc/src/sgml/ref/create_schema.sgml b/doc/src/sgml/ref/create_schema.sgml
index 79186d2b936..d8273bb2d0c 100644
--- a/doc/src/sgml/ref/create_schema.sgml
+++ b/doc/src/sgml/ref/create_schema.sgml
@@ -100,7 +100,8 @@ CREATE SCHEMA IF NOT EXISTS AUTHORIZATION <replaceable class="parameter">role_sp
       <listitem>
        <para>
         An SQL statement defining an object to be created within the
-        schema. Currently, only <command>CREATE DOMAIN</command>
+        schema. Currently, only <command>CREATE COLLATION</command>,
+        <command>CREATE DOMAIN</command>
         <command>CREATE TABLE</command>, <command>CREATE VIEW</command>, <command>CREATE
         INDEX</command>, <command>CREATE SEQUENCE</command>, <command>CREATE
         TRIGGER</command> and <command>GRANT</command> are accepted as clauses
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index fa6c6df598a..f32052084dc 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -2621,6 +2621,24 @@ read_objtype_from_string(const char *objtype)
 	return -1;					/* keep compiler quiet */
 }
 
+/* get the ObjectType name */
+const char *
+stringify_objtype(ObjectType objtype)
+{
+	for (int i = 0; i < lengthof(ObjectTypeMap); i++)
+	{
+		if (ObjectTypeMap[i].tm_type == objtype)
+			return ObjectTypeMap[i].tm_name;
+	}
+
+	ereport(ERROR,
+			errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+			errmsg("unrecognized object type %d", objtype));
+
+	return NULL;				/* keep compiler quiet */
+}
+
+
 /*
  * Interfaces to reference fields of ObjectPropertyType
  */
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 54e09d0ceb5..07b7bf0ab5c 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -1638,6 +1638,7 @@ schema_stmt:
 			| GrantStmt
 			| ViewStmt
 			| CreateDomainStmt
+			| DefineStmt
 		;
 
 
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 849a19c4e47..af164e360b5 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -64,6 +64,7 @@
 #include "rewrite/rewriteManip.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
+#include "utils/formatting.h"
 #include "utils/lsyscache.h"
 #include "utils/partcache.h"
 #include "utils/rel.h"
@@ -4484,6 +4485,45 @@ transformCreateSchemaStmtElements(ParseState *pstate, List *schemaElts,
 				}
 				break;
 
+			case T_DefineStmt:
+				{
+					char	   *coll_schema = NULL;
+					char	   *collName;
+					char	   *obj_type;
+
+					DefineStmt *stmt = castNode(DefineStmt, element);
+
+					obj_type = asc_toupper(stringify_objtype(stmt->kind),
+										   strlen(stringify_objtype(stmt->kind)));
+
+					if (stmt->kind != OBJECT_COLLATION)
+						ereport(ERROR,
+								errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								errmsg("CREATE SCHEMA ... CREATE %s currently not supported", obj_type));
+
+					DeconstructQualifiedName(stmt->defnames, &coll_schema, &collName);
+
+					if (coll_schema && strcmp(coll_schema, schemaName) != 0)
+						ereport(ERROR,
+								errcode(ERRCODE_INVALID_SCHEMA_DEFINITION),
+								errmsg("CREATE %s specifies a schema (%s) different from the one being created (%s)",
+									   obj_type, coll_schema, schemaName));
+
+					elements = lappend(elements, element);
+				}
+				break;
+
+				/*
+				 * gram.y classifies these as DefineStmt as well; therefore,
+				 * we must explicitly raise an error for these cases.
+				 */
+			case T_CompositeTypeStmt:
+			case T_CreateEnumStmt:
+			case T_CreateRangeStmt:
+				ereport(ERROR,
+						errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						errmsg("CREATE SCHEMA ... CREATE TYPE currently not supported"));
+				break;
 			default:
 				elog(ERROR, "unrecognized node type: %d",
 					 (int) nodeTag(element));
diff --git a/src/include/catalog/objectaddress.h b/src/include/catalog/objectaddress.h
index 630434b73cf..96e6abc9ffd 100644
--- a/src/include/catalog/objectaddress.h
+++ b/src/include/catalog/objectaddress.h
@@ -79,6 +79,7 @@ extern char *getObjectDescription(const ObjectAddress *object,
 extern char *getObjectDescriptionOids(Oid classid, Oid objid);
 
 extern int	read_objtype_from_string(const char *objtype);
+extern const char *stringify_objtype(ObjectType objtype);
 extern char *getObjectTypeDescription(const ObjectAddress *object,
 									  bool missing_ok);
 extern char *getObjectIdentity(const ObjectAddress *object,
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 d73c4702051..2a905b28600 100644
--- a/src/test/modules/test_ddl_deparse/expected/create_schema.out
+++ b/src/test/modules/test_ddl_deparse/expected/create_schema.out
@@ -14,8 +14,10 @@ NOTICE:  schema "baz" already exists, skipping
 CREATE SCHEMA element_test
   CREATE TABLE foo (id int)
   CREATE VIEW bar AS SELECT * FROM foo
-  CREATE DOMAIN d1 AS INT;
+  CREATE DOMAIN d1 AS INT
+  CREATE COLLATION coll (LOCALE="C");
 NOTICE:  DDL test: type simple, tag CREATE SCHEMA
 NOTICE:  DDL test: type simple, tag CREATE TABLE
 NOTICE:  DDL test: type simple, tag CREATE VIEW
 NOTICE:  DDL test: type simple, tag CREATE DOMAIN
+NOTICE:  DDL test: type simple, tag CREATE COLLATION
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 57ada462070..9727408dc69 100644
--- a/src/test/modules/test_ddl_deparse/sql/create_schema.sql
+++ b/src/test/modules/test_ddl_deparse/sql/create_schema.sql
@@ -15,4 +15,5 @@ CREATE SCHEMA IF NOT EXISTS baz;
 CREATE SCHEMA element_test
   CREATE TABLE foo (id int)
   CREATE VIEW bar AS SELECT * FROM foo
-  CREATE DOMAIN d1 AS INT;
+  CREATE DOMAIN d1 AS INT
+  CREATE COLLATION coll (LOCALE="C");
\ No newline at end of file
diff --git a/src/test/regress/expected/collate.icu.utf8.out b/src/test/regress/expected/collate.icu.utf8.out
index 8023014fe63..7e54e43363f 100644
--- a/src/test/regress/expected/collate.icu.utf8.out
+++ b/src/test/regress/expected/collate.icu.utf8.out
@@ -1295,6 +1295,21 @@ DROP TABLE test7;
 CREATE COLLATION testcoll_rulesx (provider = icu, locale = '', rules = '!!wrong!!');
 NOTICE:  using standard form "und" for ICU locale ""
 ERROR:  could not open collator for locale "und" with rules "!!wrong!!": U_INVALID_FORMAT_ERROR
+--CREATE SCHEMA CREATE COLLATION
+CREATE SCHEMA regress_schema_4
+  CREATE COLLATION coll_icu_und FROM "und-x-icu"
+  CREATE TABLE tts(a TEXT COLLATE coll_icu_und);
+\dO regress_schema_4.*
+                                         List of collations
+      Schema      |     Name     | Provider | Collate | Ctype | Locale | ICU Rules | Deterministic? 
+------------------+--------------+----------+---------+-------+--------+-----------+----------------
+ regress_schema_4 | coll_icu_und | icu      |         |       | und    |           | yes
+(1 row)
+
+DROP SCHEMA regress_schema_4 CASCADE;
+NOTICE:  drop cascades to 2 other objects
+DETAIL:  drop cascades to collation regress_schema_4.coll_icu_und
+drop cascades to table regress_schema_4.tts
 -- nondeterministic collations
 CREATE COLLATION ctest_det (provider = icu, locale = '', deterministic = true);
 NOTICE:  using standard form "und" for ICU locale ""
diff --git a/src/test/regress/expected/create_schema.out b/src/test/regress/expected/create_schema.out
index 0533c29a311..3e061c7a0ef 100644
--- a/src/test/regress/expected/create_schema.out
+++ b/src/test/regress/expected/create_schema.out
@@ -177,6 +177,41 @@ CREATE SCHEMA regress_schema_3 AUTHORIZATION CURRENT_ROLE
  regress_schema_3 | ss1  | regress_schema_3.ss |           |          | 'hello'::text | 
 (2 rows)
 
+-- Cases where the schema creation with collations
+--fail. can not CREATE COLLATION to other schema
+CREATE SCHEMA regress_schema_4 AUTHORIZATION CURRENT_ROLE
+  CREATE COLLATION public.coll_icu_und FROM "und-x-icu";
+ERROR:  CREATE COLLATION specifies a schema (public) different from the one being created (regress_schema_4)
+--fail. improper qualified name
+CREATE SCHEMA regress_schema_4 AUTHORIZATION CURRENT_ROLE
+  CREATE COLLATION postgres.public.coll_icu_und FROM "und-x-icu";
+ERROR:  cross-database references are not implemented: postgres.public.coll_icu_und
+--fail. only support collation object for DefineStmt node
+CREATE SCHEMA regress_schema_4 AUTHORIZATION CURRENT_ROLE
+  CREATE AGGREGATE balk(int4) (SFUNC = int4_sum(int8, int4), STYPE = int8, PARALLEL = SAFE, INITCOND = '0');
+ERROR:  CREATE SCHEMA ... CREATE AGGREGATE currently not supported
+--ok, qualified schema name for collation should be same as the created schema
+CREATE SCHEMA regress_schema_4 AUTHORIZATION CURRENT_ROLE
+  CREATE COLLATION regress_schema_4.coll (LOCALE="C")
+  CREATE TABLE t(a TEXT COLLATE regress_schema_4.coll);
+\dO regress_schema_4.*
+                                     List of collations
+      Schema      | Name | Provider | Collate | Ctype | Locale | ICU Rules | Deterministic? 
+------------------+------+----------+---------+-------+--------+-----------+----------------
+ regress_schema_4 | coll | libc     | C       | C     |        |           | yes
+(1 row)
+
+--ok, no qualified schema name for collation
+CREATE SCHEMA regress_schema_5 AUTHORIZATION CURRENT_ROLE
+  CREATE COLLATION coll (LOCALE="C")
+  CREATE TABLE t(a TEXT COLLATE coll);
+\dO regress_schema_5.*
+                                     List of collations
+      Schema      | Name | Provider | Collate | Ctype | Locale | ICU Rules | Deterministic? 
+------------------+------+----------+---------+-------+--------+-----------+----------------
+ regress_schema_5 | coll | libc     | C       | C     |        |           | yes
+(1 row)
+
 DROP SCHEMA regress_schema_2 CASCADE;
 NOTICE:  drop cascades to 2 other objects
 DETAIL:  drop cascades to type regress_schema_2.ss
@@ -187,5 +222,13 @@ DETAIL:  drop cascades to type regress_schema_3.ss
 drop cascades to type regress_schema_3.ss1
 drop cascades to view regress_schema_3.test
 drop cascades to table regress_schema_3.t
+DROP SCHEMA regress_schema_4 CASCADE;
+NOTICE:  drop cascades to 2 other objects
+DETAIL:  drop cascades to collation regress_schema_4.coll
+drop cascades to table regress_schema_4.t
+DROP SCHEMA regress_schema_5 CASCADE;
+NOTICE:  drop cascades to 2 other objects
+DETAIL:  drop cascades to collation regress_schema_5.coll
+drop cascades to table regress_schema_5.t
 -- Clean up
 DROP ROLE regress_create_schema_role;
diff --git a/src/test/regress/sql/collate.icu.utf8.sql b/src/test/regress/sql/collate.icu.utf8.sql
index b6c54503d21..243d69e4d32 100644
--- a/src/test/regress/sql/collate.icu.utf8.sql
+++ b/src/test/regress/sql/collate.icu.utf8.sql
@@ -513,6 +513,13 @@ DROP TABLE test7;
 
 CREATE COLLATION testcoll_rulesx (provider = icu, locale = '', rules = '!!wrong!!');
 
+--CREATE SCHEMA CREATE COLLATION
+CREATE SCHEMA regress_schema_4
+  CREATE COLLATION coll_icu_und FROM "und-x-icu"
+  CREATE TABLE tts(a TEXT COLLATE coll_icu_und);
+\dO regress_schema_4.*
+DROP SCHEMA regress_schema_4 CASCADE;
+
 
 -- nondeterministic collations
 
diff --git a/src/test/regress/sql/create_schema.sql b/src/test/regress/sql/create_schema.sql
index 54a07054767..0f802bcaffe 100644
--- a/src/test/regress/sql/create_schema.sql
+++ b/src/test/regress/sql/create_schema.sql
@@ -104,8 +104,37 @@ CREATE SCHEMA regress_schema_3 AUTHORIZATION CURRENT_ROLE
   CREATE TABLE t(a ss1);
 \dD regress_schema_3.*
 
+-- Cases where the schema creation with collations
+--fail. can not CREATE COLLATION to other schema
+CREATE SCHEMA regress_schema_4 AUTHORIZATION CURRENT_ROLE
+  CREATE COLLATION public.coll_icu_und FROM "und-x-icu";
+
+--fail. improper qualified name
+CREATE SCHEMA regress_schema_4 AUTHORIZATION CURRENT_ROLE
+  CREATE COLLATION postgres.public.coll_icu_und FROM "und-x-icu";
+
+--fail. only support collation object for DefineStmt node
+CREATE SCHEMA regress_schema_4 AUTHORIZATION CURRENT_ROLE
+  CREATE AGGREGATE balk(int4) (SFUNC = int4_sum(int8, int4), STYPE = int8, PARALLEL = SAFE, INITCOND = '0');
+
+--ok, qualified schema name for collation should be same as the created schema
+CREATE SCHEMA regress_schema_4 AUTHORIZATION CURRENT_ROLE
+  CREATE COLLATION regress_schema_4.coll (LOCALE="C")
+  CREATE TABLE t(a TEXT COLLATE regress_schema_4.coll);
+
+\dO regress_schema_4.*
+
+--ok, no qualified schema name for collation
+CREATE SCHEMA regress_schema_5 AUTHORIZATION CURRENT_ROLE
+  CREATE COLLATION coll (LOCALE="C")
+  CREATE TABLE t(a TEXT COLLATE coll);
+\dO regress_schema_5.*
+
+
 DROP SCHEMA regress_schema_2 CASCADE;
 DROP SCHEMA regress_schema_3 CASCADE;
+DROP SCHEMA regress_schema_4 CASCADE;
+DROP SCHEMA regress_schema_5 CASCADE;
 
 -- Clean up
 DROP ROLE regress_create_schema_role;
-- 
2.34.1



  [text/x-patch] v8-0004-CREATE-SCHEMA-CREATE-TYPE.patch (15.4K, 3-v8-0004-CREATE-SCHEMA-CREATE-TYPE.patch)
  download | inline diff:
From 1d4d10a467adf7ffed45f73dd56166c6fedd5e6d Mon Sep 17 00:00:00 2001
From: jian he <[email protected]>
Date: Tue, 30 Dec 2025 13:48:06 +0800
Subject: [PATCH v8 4/5] CREATE SCHEMA CREATE TYPE

The SQL standard allows types to be specified in a CREATE SCHEMA statement.
This adds support for that capability.

For example:
    CREATE SCHEMA schema_name CREATE TYPE ss;

The type will be created within the to be created schema.  The type name can be
schema-qualified or database-qualified, however it's not allowed to let type
create within a different schema

Discussion: https://postgr.es/m/CALdSSPh4jUSDsWu3K58hjO60wnTRR0DuO4CKRcwa8EVuOSfXxg@mail.gmail.com
---
 doc/src/sgml/ref/create_schema.sgml           |  3 +-
 src/backend/parser/parse_utilcmd.c            | 52 ++++++++++--
 .../expected/create_schema.out                | 10 ++-
 .../test_ddl_deparse/sql/create_schema.sql    |  6 +-
 src/test/regress/expected/create_schema.out   | 83 +++++++++++++++++++
 src/test/regress/expected/create_type.out     | 12 +++
 src/test/regress/sql/create_schema.sql        | 39 +++++++++
 src/test/regress/sql/create_type.sql          | 12 +++
 8 files changed, 206 insertions(+), 11 deletions(-)

diff --git a/doc/src/sgml/ref/create_schema.sgml b/doc/src/sgml/ref/create_schema.sgml
index d8273bb2d0c..905e966e30e 100644
--- a/doc/src/sgml/ref/create_schema.sgml
+++ b/doc/src/sgml/ref/create_schema.sgml
@@ -104,7 +104,8 @@ CREATE SCHEMA IF NOT EXISTS AUTHORIZATION <replaceable class="parameter">role_sp
         <command>CREATE DOMAIN</command>
         <command>CREATE TABLE</command>, <command>CREATE VIEW</command>, <command>CREATE
         INDEX</command>, <command>CREATE SEQUENCE</command>, <command>CREATE
-        TRIGGER</command> and <command>GRANT</command> are accepted as clauses
+        TRIGGER</command>, <command>CREATE TYPE</command> and
+        <command>GRANT</command> are accepted as clauses
         within <command>CREATE SCHEMA</command>. Other kinds of objects may
         be created in separate commands after the schema is created.
        </para>
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index af164e360b5..c2a8afedb3e 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -4496,7 +4496,7 @@ transformCreateSchemaStmtElements(ParseState *pstate, List *schemaElts,
 					obj_type = asc_toupper(stringify_objtype(stmt->kind),
 										   strlen(stringify_objtype(stmt->kind)));
 
-					if (stmt->kind != OBJECT_COLLATION)
+					if (stmt->kind != OBJECT_COLLATION && stmt->kind != OBJECT_TYPE)
 						ereport(ERROR,
 								errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 								errmsg("CREATE SCHEMA ... CREATE %s currently not supported", obj_type));
@@ -4513,16 +4513,52 @@ transformCreateSchemaStmtElements(ParseState *pstate, List *schemaElts,
 				}
 				break;
 
-				/*
-				 * gram.y classifies these as DefineStmt as well; therefore,
-				 * we must explicitly raise an error for these cases.
-				 */
 			case T_CompositeTypeStmt:
+				{
+					CompositeTypeStmt *stmt = castNode(CompositeTypeStmt, element);
+
+					checkSchemaName(pstate, schemaName, stmt->typevar);
+
+					elements = lappend(elements, element);
+				}
+				break;
+
 			case T_CreateEnumStmt:
+				{
+					char	   *typschema = NULL;
+					char	   *typname;
+
+					CreateEnumStmt *stmt = castNode(CreateEnumStmt, element);
+
+					DeconstructQualifiedName(stmt->typeName, &typschema, &typname);
+
+					if (typschema && strcmp(typschema, schemaName) != 0)
+						ereport(ERROR,
+								errcode(ERRCODE_INVALID_SCHEMA_DEFINITION),
+								errmsg("CREATE TYPE specifies a schema (%s) different from the one being created (%s)",
+									   typschema, schemaName));
+
+					elements = lappend(elements, element);
+				}
+				break;
+
 			case T_CreateRangeStmt:
-				ereport(ERROR,
-						errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-						errmsg("CREATE SCHEMA ... CREATE TYPE currently not supported"));
+				{
+					char	   *typschema = NULL;
+					char	   *typname;
+
+					CreateRangeStmt *stmt = castNode(CreateRangeStmt, element);
+
+					DeconstructQualifiedName(stmt->typeName, &typschema, &typname);
+
+					if (typschema && strcmp(typschema, schemaName) != 0)
+						ereport(ERROR,
+								errcode(ERRCODE_INVALID_SCHEMA_DEFINITION),
+								errmsg("CREATE TYPE specifies a schema (%s) different from the one being created (%s)",
+									   schemaName, typschema));
+
+					elements = lappend(elements, element);
+				}
 				break;
 			default:
 				elog(ERROR, "unrecognized node type: %d",
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 2a905b28600..b2c85682b20 100644
--- a/src/test/modules/test_ddl_deparse/expected/create_schema.out
+++ b/src/test/modules/test_ddl_deparse/expected/create_schema.out
@@ -15,9 +15,17 @@ CREATE SCHEMA element_test
   CREATE TABLE foo (id int)
   CREATE VIEW bar AS SELECT * FROM foo
   CREATE DOMAIN d1 AS INT
-  CREATE COLLATION coll (LOCALE="C");
+  CREATE COLLATION coll (LOCALE="C")
+  CREATE TYPE floatrange AS RANGE (subtype = float8, subtype_diff = float8mi)
+  CREATE TYPE ss AS (a int)
+  CREATE TYPE sss
+  CREATE TYPE rainbow AS ENUM ('red', 'orange');
 NOTICE:  DDL test: type simple, tag CREATE SCHEMA
 NOTICE:  DDL test: type simple, tag CREATE TABLE
 NOTICE:  DDL test: type simple, tag CREATE VIEW
 NOTICE:  DDL test: type simple, tag CREATE DOMAIN
 NOTICE:  DDL test: type simple, tag CREATE COLLATION
+NOTICE:  DDL test: type simple, tag CREATE TYPE
+NOTICE:  DDL test: type simple, tag CREATE TYPE
+NOTICE:  DDL test: type simple, tag CREATE TYPE
+NOTICE:  DDL test: type simple, tag CREATE TYPE
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 9727408dc69..84b35ee6fe6 100644
--- a/src/test/modules/test_ddl_deparse/sql/create_schema.sql
+++ b/src/test/modules/test_ddl_deparse/sql/create_schema.sql
@@ -16,4 +16,8 @@ CREATE SCHEMA element_test
   CREATE TABLE foo (id int)
   CREATE VIEW bar AS SELECT * FROM foo
   CREATE DOMAIN d1 AS INT
-  CREATE COLLATION coll (LOCALE="C");
\ No newline at end of file
+  CREATE COLLATION coll (LOCALE="C")
+  CREATE TYPE floatrange AS RANGE (subtype = float8, subtype_diff = float8mi)
+  CREATE TYPE ss AS (a int)
+  CREATE TYPE sss
+  CREATE TYPE rainbow AS ENUM ('red', 'orange');
\ No newline at end of file
diff --git a/src/test/regress/expected/create_schema.out b/src/test/regress/expected/create_schema.out
index 3e061c7a0ef..a33cbd0c0d9 100644
--- a/src/test/regress/expected/create_schema.out
+++ b/src/test/regress/expected/create_schema.out
@@ -212,6 +212,76 @@ CREATE SCHEMA regress_schema_5 AUTHORIZATION CURRENT_ROLE
  regress_schema_5 | coll | libc     | C       | C     |        |           | yes
 (1 row)
 
+-----CREATE SCHEMA CREATE TYPE
+--fail. can not CREATE TYPE to other schema
+CREATE SCHEMA regress_schema_6 CREATE TYPE public.ss AS (a int);
+ERROR:  CREATE specifies a schema (public) different from the one being created (regress_schema_6)
+LINE 1: CREATE SCHEMA regress_schema_6 CREATE TYPE public.ss AS (a i...
+                                                   ^
+CREATE SCHEMA regress_schema_6 CREATE TYPE public.ss;
+ERROR:  CREATE TYPE specifies a schema (public) different from the one being created (regress_schema_6)
+CREATE SCHEMA regress_schema_6 CREATE TYPE public.rainbow AS ENUM ('red', 'orange');
+ERROR:  CREATE TYPE specifies a schema (public) different from the one being created (regress_schema_6)
+CREATE SCHEMA regress_schema_6 CREATE TYPE public.floatrange AS RANGE (subtype = float8, subtype_diff = float8mi);
+ERROR:  CREATE TYPE specifies a schema (regress_schema_6) different from the one being created (public)
+--fail. improper qualified name
+CREATE SCHEMA regress_schema_6 AUTHORIZATION CURRENT_ROLE
+  CREATE TYPE postgres.public.floatrange AS RANGE (subtype = float8, subtype_diff = float8mi);
+ERROR:  cross-database references are not implemented: postgres.public.floatrange
+--the following tests should error, because we execute subcommands in order
+CREATE SCHEMA regress_schema_6 CREATE TABLE tts(a ss) CREATE TYPE ss;
+ERROR:  type "ss" does not exist
+LINE 1: CREATE SCHEMA regress_schema_6 CREATE TABLE tts(a ss) CREATE...
+                                                          ^
+CREATE SCHEMA regress_schema_6 CREATE TABLE tts(a ss) CREATE TYPE ss AS (a int);
+ERROR:  type "ss" does not exist
+LINE 1: CREATE SCHEMA regress_schema_6 CREATE TABLE tts(a ss) CREATE...
+                                                          ^
+CREATE SCHEMA regress_schema_6 CREATE TABLE tts(a rainbow) CREATE TYPE rainbow AS ENUM ('red', 'orange');
+ERROR:  type "rainbow" does not exist
+LINE 1: CREATE SCHEMA regress_schema_6 CREATE TABLE tts(a rainbow) C...
+                                                          ^
+CREATE SCHEMA regress_schema_6
+  CREATE TABLE tts(a floatrange)
+  CREATE TYPE floatrange AS RANGE (subtype = float8, subtype_diff = float8mi);
+ERROR:  type "floatrange" does not exist
+LINE 2:   CREATE TABLE tts(a floatrange)
+                             ^
+--ok
+CREATE SCHEMA regress_schema_6
+  CREATE TYPE floatrange AS RANGE (subtype = float8, subtype_diff = float8mi)
+  CREATE TYPE ss AS (a int)
+  CREATE TYPE sss
+  CREATE TYPE rainbow AS ENUM ('red', 'orange');
+\dT regress_schema_6.*
+                        List of data types
+      Schema      |               Name               | Description 
+------------------+----------------------------------+-------------
+ regress_schema_6 | regress_schema_6.floatmultirange | 
+ regress_schema_6 | regress_schema_6.floatrange      | 
+ regress_schema_6 | regress_schema_6.rainbow         | 
+ regress_schema_6 | regress_schema_6.ss              | 
+ regress_schema_6 | regress_schema_6.sss             | 
+(5 rows)
+
+--schema qualified, ok
+CREATE SCHEMA regress_schema_7
+  CREATE TYPE regress_schema_7.floatrange AS RANGE (subtype = float8, subtype_diff = float8mi)
+  CREATE TYPE regress_schema_7.ss AS (a int)
+  CREATE TYPE regress_schema_7.sss
+  CREATE TYPE regress_schema_7.rainbow AS ENUM ('red', 'orange')
+  CREATE TABLE t(a floatrange, b ss, c rainbow);
+\dT regress_schema_7.*
+                        List of data types
+      Schema      |               Name               | Description 
+------------------+----------------------------------+-------------
+ regress_schema_7 | regress_schema_7.floatmultirange | 
+ regress_schema_7 | regress_schema_7.floatrange      | 
+ regress_schema_7 | regress_schema_7.rainbow         | 
+ regress_schema_7 | regress_schema_7.ss              | 
+ regress_schema_7 | regress_schema_7.sss             | 
+(5 rows)
+
 DROP SCHEMA regress_schema_2 CASCADE;
 NOTICE:  drop cascades to 2 other objects
 DETAIL:  drop cascades to type regress_schema_2.ss
@@ -230,5 +300,18 @@ DROP SCHEMA regress_schema_5 CASCADE;
 NOTICE:  drop cascades to 2 other objects
 DETAIL:  drop cascades to collation regress_schema_5.coll
 drop cascades to table regress_schema_5.t
+DROP SCHEMA regress_schema_6 CASCADE;
+NOTICE:  drop cascades to 4 other objects
+DETAIL:  drop cascades to type regress_schema_6.floatrange
+drop cascades to type regress_schema_6.ss
+drop cascades to type regress_schema_6.sss
+drop cascades to type regress_schema_6.rainbow
+DROP SCHEMA regress_schema_7 CASCADE;
+NOTICE:  drop cascades to 5 other objects
+DETAIL:  drop cascades to type regress_schema_7.floatrange
+drop cascades to type regress_schema_7.ss
+drop cascades to type regress_schema_7.sss
+drop cascades to type regress_schema_7.rainbow
+drop cascades to table regress_schema_7.t
 -- Clean up
 DROP ROLE regress_create_schema_role;
diff --git a/src/test/regress/expected/create_type.out b/src/test/regress/expected/create_type.out
index 5181c4290b4..68decc41bb0 100644
--- a/src/test/regress/expected/create_type.out
+++ b/src/test/regress/expected/create_type.out
@@ -35,6 +35,18 @@ CREATE FUNCTION int44out(city_budget)
 NOTICE:  argument type city_budget is only a shell
 LINE 1: CREATE FUNCTION int44out(city_budget)
                                  ^
+----- CREATE SCHEMA CREATE TYPE
+--error, cannot CREATE TYPE to other schema
+CREATE SCHEMA regress_schema_1
+   CREATE TYPE public.widget (
+      internallength = 24,
+      input = widget_in,
+      output = widget_out,
+      typmod_in = numerictypmodin,
+      typmod_out = numerictypmodout,
+      alignment = double
+   );
+ERROR:  CREATE TYPE specifies a schema (public) different from the one being created (regress_schema_1)
 CREATE TYPE widget (
    internallength = 24,
    input = widget_in,
diff --git a/src/test/regress/sql/create_schema.sql b/src/test/regress/sql/create_schema.sql
index 0f802bcaffe..75fd928eacb 100644
--- a/src/test/regress/sql/create_schema.sql
+++ b/src/test/regress/sql/create_schema.sql
@@ -131,10 +131,49 @@ CREATE SCHEMA regress_schema_5 AUTHORIZATION CURRENT_ROLE
 \dO regress_schema_5.*
 
 
+
+-----CREATE SCHEMA CREATE TYPE
+--fail. can not CREATE TYPE to other schema
+CREATE SCHEMA regress_schema_6 CREATE TYPE public.ss AS (a int);
+CREATE SCHEMA regress_schema_6 CREATE TYPE public.ss;
+CREATE SCHEMA regress_schema_6 CREATE TYPE public.rainbow AS ENUM ('red', 'orange');
+CREATE SCHEMA regress_schema_6 CREATE TYPE public.floatrange AS RANGE (subtype = float8, subtype_diff = float8mi);
+
+--fail. improper qualified name
+CREATE SCHEMA regress_schema_6 AUTHORIZATION CURRENT_ROLE
+  CREATE TYPE postgres.public.floatrange AS RANGE (subtype = float8, subtype_diff = float8mi);
+
+--the following tests should error, because we execute subcommands in order
+CREATE SCHEMA regress_schema_6 CREATE TABLE tts(a ss) CREATE TYPE ss;
+CREATE SCHEMA regress_schema_6 CREATE TABLE tts(a ss) CREATE TYPE ss AS (a int);
+CREATE SCHEMA regress_schema_6 CREATE TABLE tts(a rainbow) CREATE TYPE rainbow AS ENUM ('red', 'orange');
+CREATE SCHEMA regress_schema_6
+  CREATE TABLE tts(a floatrange)
+  CREATE TYPE floatrange AS RANGE (subtype = float8, subtype_diff = float8mi);
+
+--ok
+CREATE SCHEMA regress_schema_6
+  CREATE TYPE floatrange AS RANGE (subtype = float8, subtype_diff = float8mi)
+  CREATE TYPE ss AS (a int)
+  CREATE TYPE sss
+  CREATE TYPE rainbow AS ENUM ('red', 'orange');
+\dT regress_schema_6.*
+
+--schema qualified, ok
+CREATE SCHEMA regress_schema_7
+  CREATE TYPE regress_schema_7.floatrange AS RANGE (subtype = float8, subtype_diff = float8mi)
+  CREATE TYPE regress_schema_7.ss AS (a int)
+  CREATE TYPE regress_schema_7.sss
+  CREATE TYPE regress_schema_7.rainbow AS ENUM ('red', 'orange')
+  CREATE TABLE t(a floatrange, b ss, c rainbow);
+\dT regress_schema_7.*
+
 DROP SCHEMA regress_schema_2 CASCADE;
 DROP SCHEMA regress_schema_3 CASCADE;
 DROP SCHEMA regress_schema_4 CASCADE;
 DROP SCHEMA regress_schema_5 CASCADE;
+DROP SCHEMA regress_schema_6 CASCADE;
+DROP SCHEMA regress_schema_7 CASCADE;
 
 -- Clean up
 DROP ROLE regress_create_schema_role;
diff --git a/src/test/regress/sql/create_type.sql b/src/test/regress/sql/create_type.sql
index c25018029c2..9c8939e24e0 100644
--- a/src/test/regress/sql/create_type.sql
+++ b/src/test/regress/sql/create_type.sql
@@ -32,6 +32,18 @@ CREATE FUNCTION int44out(city_budget)
    AS :'regresslib'
    LANGUAGE C STRICT IMMUTABLE;
 
+----- CREATE SCHEMA CREATE TYPE
+--error, cannot CREATE TYPE to other schema
+CREATE SCHEMA regress_schema_1
+   CREATE TYPE public.widget (
+      internallength = 24,
+      input = widget_in,
+      output = widget_out,
+      typmod_in = numerictypmodin,
+      typmod_out = numerictypmodout,
+      alignment = double
+   );
+
 CREATE TYPE widget (
    internallength = 24,
    input = widget_in,
-- 
2.34.1



  [text/x-patch] v8-0001-Don-t-try-to-re-order-the-subcommands-of-CREATE-SCHEMA.patch (26.3K, 4-v8-0001-Don-t-try-to-re-order-the-subcommands-of-CREATE-SCHEMA.patch)
  download | inline diff:
From d12c2e257a976289d6b6e3e7174354d20d1caec1 Mon Sep 17 00:00:00 2001
From: jian he <[email protected]>
Date: Wed, 17 Dec 2025 15:31:17 +0800
Subject: [PATCH v8 1/5] Don't try to re-order the subcommands of CREATE SCHEMA
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

transformCreateSchemaStmtElements has always believed that it is
supposed to re-order the subcommands of CREATE SCHEMA into a safe
execution order.  However, it is nowhere near being capable of doing
that correctly.  Nor is there reason to think that it ever will be,
or that that is a well-defined requirement, or that there's any basis
in the SQL standard for it.  Moreover, the problem will get worse as
we add more subcommand types.  Let's just drop the whole idea and
execute the commands in the order given, which seems like a much less
astonishment-prone definition anyway.

Along the way, pass down a ParseState so that we can provide an
error cursor for the "wrong schema name" error, and fix
transformCreateSchemaStmtElements so that it doesn't scribble
on the parsetree passed to it.

Note: This will cause compatibility issue,
for example:
CREATE SCHEMA regress_schema_2
  CREATE VIEW abcd_view AS SELECT a FROM abcd
  CREATE TABLE abcd (a int);

With the patch, it will throw an error, whereas on HEAD it won’t.

Discussion: https://postgr.es/m/[email protected]
---
 doc/src/sgml/ref/create_schema.sgml         |  10 +-
 src/backend/commands/extension.c            |   7 +-
 src/backend/commands/schemacmds.c           |  15 ++-
 src/backend/parser/parse_utilcmd.c          | 136 ++++++++------------
 src/backend/tcop/utility.c                  |   3 +-
 src/include/commands/schemacmds.h           |   7 +-
 src/include/parser/parse_utilcmd.h          |   3 +-
 src/test/regress/expected/create_schema.out |  37 ++++++
 src/test/regress/expected/create_view.out   |   2 +
 src/test/regress/expected/event_trigger.out |   2 +-
 src/test/regress/expected/namespace.out     |   7 +-
 src/test/regress/sql/create_schema.sql      |   5 +
 src/test/regress/sql/namespace.sql          |   8 +-
 src/tools/pgindent/typedefs.list            |   1 -
 14 files changed, 125 insertions(+), 118 deletions(-)

diff --git a/doc/src/sgml/ref/create_schema.sgml b/doc/src/sgml/ref/create_schema.sgml
index ed69298ccc6..625793a6b67 100644
--- a/doc/src/sgml/ref/create_schema.sgml
+++ b/doc/src/sgml/ref/create_schema.sgml
@@ -193,12 +193,10 @@ CREATE VIEW hollywood.winners AS
   </para>
 
   <para>
-   The SQL standard specifies that the subcommands in <command>CREATE
-   SCHEMA</command> can appear in any order.  The present
-   <productname>PostgreSQL</productname> implementation does not
-   handle all cases of forward references in subcommands; it might
-   sometimes be necessary to reorder the subcommands in order to avoid
-   forward references.
+   <productname>PostgreSQL</productname> executes the subcommands
+   in <command>CREATE SCHEMA</command> in the order given.  Other
+   implementations may try to rearrange the subcommands into dependency
+   order, but that is hard if not impossible to do correctly.
   </para>
 
   <para>
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index c43b74e319e..58e2421b008 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1773,14 +1773,17 @@ CreateExtensionInternal(char *extensionName,
 
 		if (!OidIsValid(schemaOid))
 		{
+			ParseState *pstate = make_parsestate(NULL);
 			CreateSchemaStmt *csstmt = makeNode(CreateSchemaStmt);
 
+			pstate->p_sourcetext = "(generated CREATE SCHEMA command)";
+
 			csstmt->schemaname = schemaName;
 			csstmt->authrole = NULL;	/* will be created by current user */
 			csstmt->schemaElts = NIL;
 			csstmt->if_not_exists = false;
-			CreateSchemaCommand(csstmt, "(generated CREATE SCHEMA command)",
-								-1, -1);
+
+			CreateSchemaCommand(pstate, csstmt, -1, -1);
 
 			/*
 			 * CreateSchemaCommand includes CommandCounterIncrement, so new
diff --git a/src/backend/commands/schemacmds.c b/src/backend/commands/schemacmds.c
index 3cc1472103a..09928c58d9d 100644
--- a/src/backend/commands/schemacmds.c
+++ b/src/backend/commands/schemacmds.c
@@ -49,7 +49,7 @@ static void AlterSchemaOwner_internal(HeapTuple tup, Relation rel, Oid newOwnerI
  * a subquery.
  */
 Oid
-CreateSchemaCommand(CreateSchemaStmt *stmt, const char *queryString,
+CreateSchemaCommand(ParseState *pstate, CreateSchemaStmt *stmt,
 					int stmt_location, int stmt_len)
 {
 	const char *schemaName = stmt->schemaname;
@@ -189,12 +189,13 @@ CreateSchemaCommand(CreateSchemaStmt *stmt, const char *queryString,
 
 	/*
 	 * Examine the list of commands embedded in the CREATE SCHEMA command, and
-	 * reorganize them into a sequentially executable order with no forward
-	 * references.  Note that the result is still a list of raw parsetrees ---
-	 * we cannot, in general, run parse analysis on one statement until we
-	 * have actually executed the prior ones.
+	 * do preliminary transformations (mostly, verify that none are trying to
+	 * create objects outside the new schema).  Note that the result is still
+	 * a list of raw parsetrees --- we cannot, in general, run parse analysis
+	 * on one statement until we have actually executed the prior ones.
 	 */
-	parsetree_list = transformCreateSchemaStmtElements(stmt->schemaElts,
+	parsetree_list = transformCreateSchemaStmtElements(pstate,
+													   stmt->schemaElts,
 													   schemaName);
 
 	/*
@@ -219,7 +220,7 @@ CreateSchemaCommand(CreateSchemaStmt *stmt, const char *queryString,
 
 		/* do this step */
 		ProcessUtility(wrapper,
-					   queryString,
+					   pstate->p_sourcetext,
 					   false,
 					   PROCESS_UTILITY_SUBCOMMAND,
 					   NULL,
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 2b7b084f216..5d168fd0285 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -98,18 +98,6 @@ typedef struct
 	bool		ofType;			/* true if statement contains OF typename */
 } CreateStmtContext;
 
-/* State shared by transformCreateSchemaStmtElements and its subroutines */
-typedef struct
-{
-	const char *schemaname;		/* name of schema */
-	List	   *sequences;		/* CREATE SEQUENCE items */
-	List	   *tables;			/* CREATE TABLE items */
-	List	   *views;			/* CREATE VIEW items */
-	List	   *indexes;		/* CREATE INDEX items */
-	List	   *triggers;		/* CREATE TRIGGER items */
-	List	   *grants;			/* GRANT items */
-} CreateSchemaStmtContext;
-
 
 static void transformColumnDefinition(CreateStmtContext *cxt,
 									  ColumnDef *column);
@@ -136,7 +124,8 @@ static void transformCheckConstraints(CreateStmtContext *cxt,
 static void transformConstraintAttrs(CreateStmtContext *cxt,
 									 List *constraintList);
 static void transformColumnType(CreateStmtContext *cxt, ColumnDef *column);
-static void setSchemaName(const char *context_schema, char **stmt_schema_name);
+static void checkSchemaName(ParseState *pstate, const char *context_schema,
+							RangeVar *relation);
 static void transformPartitionCmd(CreateStmtContext *cxt, PartitionBoundSpec *bound);
 static List *transformPartitionRangeBounds(ParseState *pstate, List *blist,
 										   Relation parent);
@@ -4388,51 +4377,35 @@ transformColumnType(CreateStmtContext *cxt, ColumnDef *column)
  * transformCreateSchemaStmtElements -
  *	  analyzes the elements of a CREATE SCHEMA statement
  *
- * Split the schema element list from a CREATE SCHEMA statement into
- * individual commands and place them in the result list in an order
- * such that there are no forward references (e.g. GRANT to a table
- * created later in the list). Note that the logic we use for determining
- * forward references is presently quite incomplete.
+ * 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.
+ * 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.
  *
  * "schemaName" is the name of the schema that will be used for the creation
- * of the objects listed, that may be compiled from the schema name defined
+ * of the objects listed.  It may be obtained from the schema name defined
  * in the statement or a role specification.
  *
- * SQL also allows constraints to make forward references, so thumb through
- * the table columns and move forward references to a posterior alter-table
- * command.
- *
  * 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: this breaks the rules a little bit by modifying schema-name fields
- * within passed-in structs.  However, the transformation would be the same
- * if done over, so it should be all right to scribble on the input to this
- * extent.
  */
 List *
-transformCreateSchemaStmtElements(List *schemaElts, const char *schemaName)
+transformCreateSchemaStmtElements(ParseState *pstate, List *schemaElts,
+								  const char *schemaName)
 {
-	CreateSchemaStmtContext cxt;
-	List	   *result;
-	ListCell   *elements;
-
-	cxt.schemaname = schemaName;
-	cxt.sequences = NIL;
-	cxt.tables = NIL;
-	cxt.views = NIL;
-	cxt.indexes = NIL;
-	cxt.triggers = NIL;
-	cxt.grants = NIL;
+	List	   *elements = NIL;
+	ListCell   *lc;
 
 	/*
-	 * Run through each schema element in the schema element list. Separate
-	 * statements by type, and do preliminary analysis.
+	 * Run through each schema element in the schema element list.  Check
+	 * target schema names, and collect the list of actions to be done.
 	 */
-	foreach(elements, schemaElts)
+	foreach(lc, schemaElts)
 	{
-		Node	   *element = lfirst(elements);
+		Node	   *element = lfirst(lc);
 
 		switch (nodeTag(element))
 		{
@@ -4440,8 +4413,8 @@ transformCreateSchemaStmtElements(List *schemaElts, const char *schemaName)
 				{
 					CreateSeqStmt *elp = (CreateSeqStmt *) element;
 
-					setSchemaName(cxt.schemaname, &elp->sequence->schemaname);
-					cxt.sequences = lappend(cxt.sequences, element);
+					checkSchemaName(pstate, schemaName, elp->sequence);
+					elements = lappend(elements, element);
 				}
 				break;
 
@@ -4449,12 +4422,8 @@ transformCreateSchemaStmtElements(List *schemaElts, const char *schemaName)
 				{
 					CreateStmt *elp = (CreateStmt *) element;
 
-					setSchemaName(cxt.schemaname, &elp->relation->schemaname);
-
-					/*
-					 * XXX todo: deal with constraints
-					 */
-					cxt.tables = lappend(cxt.tables, element);
+					checkSchemaName(pstate, schemaName, elp->relation);
+					elements = lappend(elements, element);
 				}
 				break;
 
@@ -4462,12 +4431,8 @@ transformCreateSchemaStmtElements(List *schemaElts, const char *schemaName)
 				{
 					ViewStmt   *elp = (ViewStmt *) element;
 
-					setSchemaName(cxt.schemaname, &elp->view->schemaname);
-
-					/*
-					 * XXX todo: deal with references between views
-					 */
-					cxt.views = lappend(cxt.views, element);
+					checkSchemaName(pstate, schemaName, elp->view);
+					elements = lappend(elements, element);
 				}
 				break;
 
@@ -4475,8 +4440,8 @@ transformCreateSchemaStmtElements(List *schemaElts, const char *schemaName)
 				{
 					IndexStmt  *elp = (IndexStmt *) element;
 
-					setSchemaName(cxt.schemaname, &elp->relation->schemaname);
-					cxt.indexes = lappend(cxt.indexes, element);
+					checkSchemaName(pstate, schemaName, elp->relation);
+					elements = lappend(elements, element);
 				}
 				break;
 
@@ -4484,13 +4449,13 @@ transformCreateSchemaStmtElements(List *schemaElts, const char *schemaName)
 				{
 					CreateTrigStmt *elp = (CreateTrigStmt *) element;
 
-					setSchemaName(cxt.schemaname, &elp->relation->schemaname);
-					cxt.triggers = lappend(cxt.triggers, element);
+					checkSchemaName(pstate, schemaName, elp->relation);
+					elements = lappend(elements, element);
 				}
 				break;
 
 			case T_GrantStmt:
-				cxt.grants = lappend(cxt.grants, element);
+				elements = lappend(elements, element);
 				break;
 
 			default:
@@ -4499,32 +4464,39 @@ transformCreateSchemaStmtElements(List *schemaElts, const char *schemaName)
 		}
 	}
 
-	result = NIL;
-	result = list_concat(result, cxt.sequences);
-	result = list_concat(result, cxt.tables);
-	result = list_concat(result, cxt.views);
-	result = list_concat(result, cxt.indexes);
-	result = list_concat(result, cxt.triggers);
-	result = list_concat(result, cxt.grants);
-
-	return result;
+	return elements;
 }
 
 /*
- * setSchemaName
- *		Set or check schema name in an element of a CREATE SCHEMA command
+ * checkSchemaName
+ *		Check schema name in an element of a CREATE SCHEMA command
+ *
+ * It's okay if the command doesn't specify a target schema name, because
+ * CreateSchemaCommand will set up the default creation schema to be the
+ * new schema.  But if a target schema name is given, it had better match.
+ * We also have to check that the command doesn't say CREATE TEMP, since
+ * that would likewise put the object into the wrong schema.
  */
 static void
-setSchemaName(const char *context_schema, char **stmt_schema_name)
+checkSchemaName(ParseState *pstate, const char *context_schema,
+				RangeVar *relation)
 {
-	if (*stmt_schema_name == NULL)
-		*stmt_schema_name = unconstify(char *, context_schema);
-	else if (strcmp(context_schema, *stmt_schema_name) != 0)
+	if (relation->schemaname != NULL &&
+		strcmp(context_schema, relation->schemaname) != 0)
 		ereport(ERROR,
-				(errcode(ERRCODE_INVALID_SCHEMA_DEFINITION),
-				 errmsg("CREATE specifies a schema (%s) "
-						"different from the one being created (%s)",
-						*stmt_schema_name, context_schema)));
+				errcode(ERRCODE_INVALID_SCHEMA_DEFINITION),
+				errmsg("CREATE specifies a schema (%s) different from the one being created (%s)",
+					   relation->schemaname, context_schema),
+				parser_errposition(pstate, relation->location));
+
+	if (relation->relpersistence == RELPERSISTENCE_TEMP)
+	{
+		/* spell this error the same as in RangeVarAdjustRelationPersistence */
+		ereport(ERROR,
+				errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+				errmsg("cannot create temporary relation in non-temporary schema"),
+				parser_errposition(pstate, relation->location));
+	}
 }
 
 /*
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index d18a3a60a46..5e8cd97f3c3 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1119,8 +1119,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				 * relation and attribute manipulation
 				 */
 			case T_CreateSchemaStmt:
-				CreateSchemaCommand((CreateSchemaStmt *) parsetree,
-									queryString,
+				CreateSchemaCommand(pstate, castNode(CreateSchemaStmt, parsetree),
 									pstmt->stmt_location,
 									pstmt->stmt_len);
 
diff --git a/src/include/commands/schemacmds.h b/src/include/commands/schemacmds.h
index 8557176b66a..835265bb67c 100644
--- a/src/include/commands/schemacmds.h
+++ b/src/include/commands/schemacmds.h
@@ -16,12 +16,9 @@
 #define SCHEMACMDS_H
 
 #include "catalog/objectaddress.h"
-#include "nodes/parsenodes.h"
-
-extern Oid	CreateSchemaCommand(CreateSchemaStmt *stmt,
-								const char *queryString,
-								int stmt_location, int stmt_len);
+#include "parser/parse_node.h"
 
+extern Oid	CreateSchemaCommand(ParseState *pstate, CreateSchemaStmt *stmt, int stmt_location, int stmt_len);
 extern ObjectAddress RenameSchema(const char *oldname, const char *newname);
 extern ObjectAddress AlterSchemaOwner(const char *name, Oid newOwnerId);
 extern void AlterSchemaOwner_oid(Oid schemaoid, Oid newOwnerId);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..d151bba03eb 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -30,7 +30,8 @@ extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-extern List *transformCreateSchemaStmtElements(List *schemaElts,
+extern List *transformCreateSchemaStmtElements(ParseState *pstate,
+											   List *schemaElts,
 											   const char *schemaName);
 extern PartitionBoundSpec *transformPartitionBound(ParseState *pstate, Relation parent,
 												   PartitionBoundSpec *spec);
diff --git a/src/test/regress/expected/create_schema.out b/src/test/regress/expected/create_schema.out
index 93302a07efc..38530c282a9 100644
--- a/src/test/regress/expected/create_schema.out
+++ b/src/test/regress/expected/create_schema.out
@@ -9,55 +9,92 @@ CREATE ROLE regress_create_schema_role SUPERUSER;
 CREATE SCHEMA AUTHORIZATION regress_create_schema_role
   CREATE SEQUENCE schema_not_existing.seq;
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_create_schema_role)
+LINE 2:   CREATE SEQUENCE schema_not_existing.seq;
+                          ^
 CREATE SCHEMA AUTHORIZATION regress_create_schema_role
   CREATE TABLE schema_not_existing.tab (id int);
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_create_schema_role)
+LINE 2:   CREATE TABLE schema_not_existing.tab (id int);
+                       ^
 CREATE SCHEMA AUTHORIZATION regress_create_schema_role
   CREATE VIEW schema_not_existing.view AS SELECT 1;
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_create_schema_role)
+LINE 2:   CREATE VIEW schema_not_existing.view AS SELECT 1;
+                      ^
 CREATE SCHEMA AUTHORIZATION regress_create_schema_role
   CREATE INDEX ON schema_not_existing.tab (id);
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_create_schema_role)
+LINE 2:   CREATE INDEX ON schema_not_existing.tab (id);
+                          ^
 CREATE SCHEMA AUTHORIZATION regress_create_schema_role
   CREATE TRIGGER schema_trig BEFORE INSERT ON schema_not_existing.tab
   EXECUTE FUNCTION schema_trig.no_func();
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_create_schema_role)
+LINE 2:   CREATE TRIGGER schema_trig BEFORE INSERT ON schema_not_exi...
+                                                      ^
 -- Again, with a role specification and no schema names.
 SET ROLE regress_create_schema_role;
 CREATE SCHEMA AUTHORIZATION CURRENT_ROLE
   CREATE SEQUENCE schema_not_existing.seq;
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_create_schema_role)
+LINE 2:   CREATE SEQUENCE schema_not_existing.seq;
+                          ^
 CREATE SCHEMA AUTHORIZATION CURRENT_ROLE
   CREATE TABLE schema_not_existing.tab (id int);
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_create_schema_role)
+LINE 2:   CREATE TABLE schema_not_existing.tab (id int);
+                       ^
 CREATE SCHEMA AUTHORIZATION CURRENT_ROLE
   CREATE VIEW schema_not_existing.view AS SELECT 1;
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_create_schema_role)
+LINE 2:   CREATE VIEW schema_not_existing.view AS SELECT 1;
+                      ^
 CREATE SCHEMA AUTHORIZATION CURRENT_ROLE
   CREATE INDEX ON schema_not_existing.tab (id);
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_create_schema_role)
+LINE 2:   CREATE INDEX ON schema_not_existing.tab (id);
+                          ^
 CREATE SCHEMA AUTHORIZATION CURRENT_ROLE
   CREATE TRIGGER schema_trig BEFORE INSERT ON schema_not_existing.tab
   EXECUTE FUNCTION schema_trig.no_func();
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_create_schema_role)
+LINE 2:   CREATE TRIGGER schema_trig BEFORE INSERT ON schema_not_exi...
+                                                      ^
 -- Again, with a schema name and a role specification.
 CREATE SCHEMA regress_schema_1 AUTHORIZATION CURRENT_ROLE
   CREATE SEQUENCE schema_not_existing.seq;
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_schema_1)
+LINE 2:   CREATE SEQUENCE schema_not_existing.seq;
+                          ^
 CREATE SCHEMA regress_schema_1 AUTHORIZATION CURRENT_ROLE
   CREATE TABLE schema_not_existing.tab (id int);
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_schema_1)
+LINE 2:   CREATE TABLE schema_not_existing.tab (id int);
+                       ^
 CREATE SCHEMA regress_schema_1 AUTHORIZATION CURRENT_ROLE
   CREATE VIEW schema_not_existing.view AS SELECT 1;
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_schema_1)
+LINE 2:   CREATE VIEW schema_not_existing.view AS SELECT 1;
+                      ^
 CREATE SCHEMA regress_schema_1 AUTHORIZATION CURRENT_ROLE
   CREATE INDEX ON schema_not_existing.tab (id);
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_schema_1)
+LINE 2:   CREATE INDEX ON schema_not_existing.tab (id);
+                          ^
 CREATE SCHEMA regress_schema_1 AUTHORIZATION CURRENT_ROLE
   CREATE TRIGGER schema_trig BEFORE INSERT ON schema_not_existing.tab
   EXECUTE FUNCTION schema_trig.no_func();
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_schema_1)
+LINE 2:   CREATE TRIGGER schema_trig BEFORE INSERT ON schema_not_exi...
+                                                      ^
 RESET ROLE;
+--error. Forward reference is not allowed.
+CREATE SCHEMA regress_schema_2
+  CREATE VIEW abcd_view AS SELECT a FROM abcd
+  CREATE TABLE abcd (a int);
+ERROR:  relation "abcd" does not exist
+LINE 2:   CREATE VIEW abcd_view AS SELECT a FROM abcd
+                                                 ^
 -- Cases where the schema creation succeeds.
 -- The schema created matches the role name.
 CREATE SCHEMA AUTHORIZATION regress_create_schema_role
diff --git a/src/test/regress/expected/create_view.out b/src/test/regress/expected/create_view.out
index bc4f79938b3..63cf4b4371d 100644
--- a/src/test/regress/expected/create_view.out
+++ b/src/test/regress/expected/create_view.out
@@ -128,6 +128,8 @@ ERROR:  cannot create temporary relation in non-temporary schema
 CREATE SCHEMA test_view_schema
     CREATE TEMP VIEW testview AS SELECT 1;
 ERROR:  cannot create temporary relation in non-temporary schema
+LINE 2:     CREATE TEMP VIEW testview AS SELECT 1;
+                             ^
 -- joins: if any of the join relations are temporary, the view
 -- should also be temporary
 -- should be non-temp
diff --git a/src/test/regress/expected/event_trigger.out b/src/test/regress/expected/event_trigger.out
index 16e4530708c..4a7fd2bc59a 100644
--- a/src/test/regress/expected/event_trigger.out
+++ b/src/test/regress/expected/event_trigger.out
@@ -424,12 +424,12 @@ NOTICE:  END: command_tag=CREATE TABLE type=table identity=evttrig.one
 NOTICE:  END: command_tag=CREATE INDEX type=index identity=evttrig.one_pkey
 NOTICE:  END: command_tag=ALTER SEQUENCE type=sequence identity=evttrig.one_col_a_seq
 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 INDEX type=index identity=evttrig.one_idx
 -- Partitioned tables with a partitioned index
 CREATE TABLE evttrig.parted (
     id int PRIMARY KEY)
diff --git a/src/test/regress/expected/namespace.out b/src/test/regress/expected/namespace.out
index dbbda72d395..d02f3fd67d7 100644
--- a/src/test/regress/expected/namespace.out
+++ b/src/test/regress/expected/namespace.out
@@ -10,13 +10,10 @@ SELECT pg_catalog.set_config('search_path', ' ', false);
 (1 row)
 
 CREATE SCHEMA test_ns_schema_1
+       CREATE TABLE abc (a serial, b int UNIQUE)
        CREATE UNIQUE INDEX abc_a_idx ON abc (a)
        CREATE VIEW abc_view AS
-              SELECT a+1 AS a, b+1 AS b FROM abc
-       CREATE TABLE abc (
-              a serial,
-              b int UNIQUE
-       );
+              SELECT a+1 AS a, b+1 AS b FROM abc;
 -- verify that the correct search_path restored on abort
 SET search_path to public;
 BEGIN;
diff --git a/src/test/regress/sql/create_schema.sql b/src/test/regress/sql/create_schema.sql
index 1b7064247a1..b3dc1cfd758 100644
--- a/src/test/regress/sql/create_schema.sql
+++ b/src/test/regress/sql/create_schema.sql
@@ -47,6 +47,11 @@ CREATE SCHEMA regress_schema_1 AUTHORIZATION CURRENT_ROLE
   EXECUTE FUNCTION schema_trig.no_func();
 RESET ROLE;
 
+--error. Forward reference is not allowed.
+CREATE SCHEMA regress_schema_2
+  CREATE VIEW abcd_view AS SELECT a FROM abcd
+  CREATE TABLE abcd (a int);
+
 -- Cases where the schema creation succeeds.
 -- The schema created matches the role name.
 CREATE SCHEMA AUTHORIZATION regress_create_schema_role
diff --git a/src/test/regress/sql/namespace.sql b/src/test/regress/sql/namespace.sql
index 306cdc2d8c6..9433eb3c15c 100644
--- a/src/test/regress/sql/namespace.sql
+++ b/src/test/regress/sql/namespace.sql
@@ -7,15 +7,11 @@
 SELECT pg_catalog.set_config('search_path', ' ', false);
 
 CREATE SCHEMA test_ns_schema_1
+       CREATE TABLE abc (a serial, b int UNIQUE)
        CREATE UNIQUE INDEX abc_a_idx ON abc (a)
 
        CREATE VIEW abc_view AS
-              SELECT a+1 AS a, b+1 AS b FROM abc
-
-       CREATE TABLE abc (
-              a serial,
-              b int UNIQUE
-       );
+              SELECT a+1 AS a, b+1 AS b FROM abc;
 
 -- verify that the correct search_path restored on abort
 SET search_path to public;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index ceb3fc5d980..be588bd01e4 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -569,7 +569,6 @@ CreateRangeStmt
 CreateReplicationSlotCmd
 CreateRoleStmt
 CreateSchemaStmt
-CreateSchemaStmtContext
 CreateSeqStmt
 CreateStatsStmt
 CreateStmt
-- 
2.34.1



  [text/x-patch] v8-0005-CREATE-SCHEMA-foreign-key-executed-at-the-end.patch (21.3K, 5-v8-0005-CREATE-SCHEMA-foreign-key-executed-at-the-end.patch)
  download | inline diff:
From 8a504a948412ba11ba154d98676318983a00cfcc Mon Sep 17 00:00:00 2001
From: jian he <[email protected]>
Date: Wed, 31 Dec 2025 09:37:53 +0800
Subject: [PATCH v8 5/5] CREATE SCHEMA foreign key executed at the end

When a CREATE TABLE subcommand includes a FOREIGN KEY clause, transform that
clause into ALTER TABLE ADD FOREIGN KEY, and push it to the back of the CREATE
SCHEMA's to-do list.

For CREATE SCHEMA, all subcommands are executed in the order they are specified.
However, to support cross-referenced foreign keys, we transform foreign key
constraint definitions into separate ALTER TABLE ... ADD FOREIGN KEY commands
and append them to the end of the CREATE SCHEMA execution sequence.

All CREATE TABLE commands are executed first, so subsequent ALTER TABLE ... ADD
FOREIGN KEY statements will not encounter any conflicts.

Discussion: https://postgr.es/m/[email protected]
---
 src/backend/commands/schemacmds.c           | 134 ++++++++++++++++++++
 src/backend/parser/parse_utilcmd.c          |  36 +++---
 src/include/parser/parse_utilcmd.h          |   1 +
 src/test/regress/expected/create_schema.out |  83 ++++++++++++
 src/test/regress/expected/event_trigger.out |   2 +-
 src/test/regress/sql/create_schema.sql      |  36 ++++++
 6 files changed, 272 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/schemacmds.c b/src/backend/commands/schemacmds.c
index 09928c58d9d..8c325631abf 100644
--- a/src/backend/commands/schemacmds.c
+++ b/src/backend/commands/schemacmds.c
@@ -198,6 +198,140 @@ CreateSchemaCommand(ParseState *pstate, CreateSchemaStmt *stmt,
 													   stmt->schemaElts,
 													   schemaName);
 
+	/*
+	 * Reorder the list of commands embedded in the CREATE SCHEMA statement.
+	 * We only do this for foreign keys. Foreign key are transformed into
+	 * ALTER TABLE ... ADD FOREIGN KEY commands and moved to the end of the
+	 * CREATE SCHEMA to-do list (parsetree_list).
+	 *
+	 * At the same time, the original foreign key Constraint nodes will be
+	 * removed from the CreateStmt, since they have already been converted
+	 * into AlterTableCmds. No parse analysis is required for the original
+	 * foreign key constraint nodes: since expressions are not allowed in
+	 * foreign key constraints.
+	 *
+	 * See also transformFKConstraints and transformConstraintAttrs.
+	 */
+	foreach(parsetree_item, parsetree_list)
+	{
+		ListCell   *elements;
+		CreateStmt *csstmt;
+
+		Node	   *stmt = (Node *) lfirst(parsetree_item);
+
+		if (!IsA(stmt, CreateStmt))
+			continue;
+
+		csstmt = castNode(CreateStmt, stmt);
+
+		foreach(elements, csstmt->tableElts)
+		{
+			ColumnDef  *entry;
+			Constraint *constr;
+			AlterTableStmt *alterstmt;
+			AlterTableCmd *altercmd;
+
+			Node	   *element = lfirst(elements);
+
+			if (IsA(element, Constraint))
+			{
+				constr = castNode(Constraint, element);
+
+				if (constr->contype != CONSTR_FOREIGN)
+					continue;
+
+				alterstmt = makeNode(AlterTableStmt);
+				alterstmt->relation = copyObject(csstmt->relation);
+				alterstmt->cmds = NIL;
+				alterstmt->objtype = OBJECT_TABLE;
+
+				altercmd = makeNode(AlterTableCmd);
+				altercmd->subtype = AT_AddConstraint;
+				altercmd->name = NULL;
+				altercmd->def = (Node *) copyObject(constr);
+
+				alterstmt->cmds = lappend(alterstmt->cmds, altercmd);
+
+				/*
+				 * The foreign key has already been transformed into an
+				 * AlterTableCmd; remove the original entry from
+				 * CreateStmt.tableElts.
+				 */
+				csstmt->tableElts = foreach_delete_current(csstmt->tableElts,
+														   elements);
+
+				parsetree_list = lappend(parsetree_list, alterstmt);
+
+				continue;
+			}
+
+			if (IsA(element, ColumnDef))
+			{
+				entry = castNode(ColumnDef, element);
+
+				transformConstraintAttrs(pstate, entry->constraints);
+
+				for (int constrpos = 0; constrpos < list_length(entry->constraints); constrpos++)
+				{
+					Constraint *colconstr = list_nth_node(Constraint, entry->constraints, constrpos);
+
+					if (colconstr->contype != CONSTR_FOREIGN)
+						continue;
+
+					alterstmt = makeNode(AlterTableStmt);
+					altercmd = makeNode(AlterTableCmd);
+
+					colconstr->fk_attrs = list_make1(makeString(entry->colname));
+
+					alterstmt->relation = copyObject(csstmt->relation);
+					alterstmt->cmds = NIL;
+					alterstmt->objtype = OBJECT_TABLE;
+
+					altercmd->subtype = AT_AddConstraint;
+					altercmd->name = NULL;
+					altercmd->def = (Node *) copyObject(colconstr);
+					alterstmt->cmds = lappend(alterstmt->cmds, altercmd);
+
+					parsetree_list = lappend(parsetree_list, alterstmt);
+
+					/*
+					 * Column constraints separate the Constraint node from
+					 * its attributes; a full column-level foreign key
+					 * constraint may be represented by multiple Constraint
+					 * nodes. After transformConstraintAttrs, the main foreign
+					 * key Constraint node already contains all required
+					 * information. Therefore, when the foreign key Constraint
+					 * node is removed, the associated attribute nodes (which
+					 * are also Constraint nodes) must be removed as well.
+					 */
+					for (int restpos = constrpos + 1; restpos < list_length(entry->constraints);)
+					{
+						Constraint *nextcolconstr = list_nth_node(Constraint, entry->constraints, restpos);
+
+						if (nextcolconstr->contype == CONSTR_ATTR_DEFERRABLE ||
+							nextcolconstr->contype == CONSTR_ATTR_NOT_DEFERRABLE ||
+							nextcolconstr->contype == CONSTR_ATTR_DEFERRED ||
+							nextcolconstr->contype == CONSTR_ATTR_IMMEDIATE ||
+							nextcolconstr->contype == CONSTR_ATTR_NOT_ENFORCED)
+						{
+							entry->constraints = list_delete_nth_cell(entry->constraints, restpos);
+						}
+						else
+							break;
+					}
+
+					entry->constraints = list_delete_nth_cell(entry->constraints, constrpos);
+
+					/*
+					 * We deleted one Constraint node, so we also need update
+					 * constrpos
+					 */
+					constrpos = constrpos - 1;
+				}
+			}
+		}
+	}
+
 	/*
 	 * Execute each command contained in the CREATE SCHEMA.  Since the grammar
 	 * allows only utility commands in CREATE SCHEMA, there is no need to pass
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index c2a8afedb3e..4363df2afaf 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -122,8 +122,6 @@ static void transformFKConstraints(CreateStmtContext *cxt,
 								   bool isAddConstraint);
 static void transformCheckConstraints(CreateStmtContext *cxt,
 									  bool skipValidation);
-static void transformConstraintAttrs(CreateStmtContext *cxt,
-									 List *constraintList);
 static void transformColumnType(CreateStmtContext *cxt, ColumnDef *column);
 static void checkSchemaName(ParseState *pstate, const char *context_schema,
 							RangeVar *relation);
@@ -693,7 +691,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
@@ -4189,8 +4187,8 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
  * EXCLUSION, and PRIMARY KEY constraints, but someday they ought to be
  * supported for other constraint types.
  */
-static void
-transformConstraintAttrs(CreateStmtContext *cxt, List *constraintList)
+void
+transformConstraintAttrs(ParseState *pstate, List *constraintList)
 {
 	Constraint *lastprimarycon = NULL;
 	bool		saw_deferrability = false;
@@ -4219,12 +4217,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;
@@ -4234,12 +4232,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 &&
@@ -4247,7 +4245,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:
@@ -4255,12 +4253,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;
 
@@ -4273,7 +4271,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:
@@ -4281,12 +4279,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;
@@ -4298,12 +4296,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;
@@ -4315,12 +4313,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;
 
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index d151bba03eb..cf7bb3a5731 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -20,6 +20,7 @@ typedef struct AttrMap AttrMap; /* avoid including attmap.h here */
 
 
 extern List *transformCreateStmt(CreateStmt *stmt, const char *queryString);
+extern void transformConstraintAttrs(ParseState *pstate, List *constraintList);
 extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   const char *queryString,
 											   List **beforeStmts,
diff --git a/src/test/regress/expected/create_schema.out b/src/test/regress/expected/create_schema.out
index a33cbd0c0d9..6df41f9a2f3 100644
--- a/src/test/regress/expected/create_schema.out
+++ b/src/test/regress/expected/create_schema.out
@@ -282,6 +282,73 @@ CREATE SCHEMA regress_schema_7
  regress_schema_7 | regress_schema_7.sss             | 
 (5 rows)
 
+CREATE SCHEMA regress_schema_8
+    CREATE TABLE regress_schema_8.t2 (
+        b int,
+        a int REFERENCES t1 DEFERRABLE INITIALLY DEFERRED NOT ENFORCED
+              REFERENCES t3 DEFERRABLE INITIALLY DEFERRED NOT ENFORCED,
+        CONSTRAINT fk FOREIGN KEY (a) REFERENCES t1 DEFERRABLE INITIALLY DEFERRED NOT ENFORCED)
+    CREATE TABLE regress_schema_8.t1 (a int PRIMARY KEY)
+    CREATE TABLE t3 (a int PRIMARY KEY)
+    CREATE TABLE t4(b int,
+                    a int REFERENCES t5 DEFERRABLE INITIALLY DEFERRED NOT ENFORCED
+                          REFERENCES t6 DEFERRABLE INITIALLY DEFERRED NOT ENFORCED,
+        CONSTRAINT fk FOREIGN KEY (a) REFERENCES t6 DEFERRABLE INITIALLY DEFERRED NOT ENFORCED)
+    CREATE TABLE t5 (a int, b int, PRIMARY KEY (a))
+    CREATE TABLE t6 (a int, b int, PRIMARY KEY (a));
+\d regress_schema_8.t2
+            Table "regress_schema_8.t2"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ b      | integer |           |          | 
+ a      | integer |           |          | 
+Foreign-key constraints:
+    "fk" FOREIGN KEY (a) REFERENCES regress_schema_8.t1(a) DEFERRABLE INITIALLY DEFERRED NOT ENFORCED
+    "t2_a_fkey" FOREIGN KEY (a) REFERENCES regress_schema_8.t1(a) DEFERRABLE INITIALLY DEFERRED NOT ENFORCED
+    "t2_a_fkey1" FOREIGN KEY (a) REFERENCES regress_schema_8.t3(a) DEFERRABLE INITIALLY DEFERRED NOT ENFORCED
+
+\d regress_schema_8.t4
+            Table "regress_schema_8.t4"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ b      | integer |           |          | 
+ a      | integer |           |          | 
+Foreign-key constraints:
+    "fk" FOREIGN KEY (a) REFERENCES regress_schema_8.t6(a) DEFERRABLE INITIALLY DEFERRED NOT ENFORCED
+    "t4_a_fkey" FOREIGN KEY (a) REFERENCES regress_schema_8.t5(a) DEFERRABLE INITIALLY DEFERRED NOT ENFORCED
+    "t4_a_fkey1" FOREIGN KEY (a) REFERENCES regress_schema_8.t6(a) DEFERRABLE INITIALLY DEFERRED NOT ENFORCED
+
+CREATE SCHEMA regress_schema_9
+    CREATE TABLE t2 (a int, b int,
+        CONSTRAINT fk FOREIGN KEY (a, b) REFERENCES t1 MATCH FULL NOT DEFERRABLE INITIALLY IMMEDIATE NOT ENFORCED,
+        CONSTRAINT fk1 FOREIGN KEY (a, b) REFERENCES t3 MATCH FULL DEFERRABLE INITIALLY IMMEDIATE NOT ENFORCED)
+    CREATE TABLE t1 (a int, b int, PRIMARY KEY (b, a))
+    CREATE TABLE t3 (a int, b int, PRIMARY KEY (b, a))
+    CREATE TABLE t4 (a int, b int,
+        CONSTRAINT fk FOREIGN KEY (b) REFERENCES t6 MATCH FULL NOT DEFERRABLE INITIALLY IMMEDIATE NOT ENFORCED,
+        CONSTRAINT fk1 FOREIGN KEY (a) REFERENCES t5 MATCH FULL DEFERRABLE INITIALLY IMMEDIATE NOT ENFORCED)
+    CREATE TABLE t5 (a int, b int, PRIMARY KEY (a))
+    CREATE TABLE t6 (a int, b int, PRIMARY KEY (b));
+\d regress_schema_9.t2
+            Table "regress_schema_9.t2"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ a      | integer |           |          | 
+ b      | integer |           |          | 
+Foreign-key constraints:
+    "fk" FOREIGN KEY (a, b) REFERENCES regress_schema_9.t1(b, a) MATCH FULL NOT ENFORCED
+    "fk1" FOREIGN KEY (a, b) REFERENCES regress_schema_9.t3(b, a) MATCH FULL DEFERRABLE NOT ENFORCED
+
+\d regress_schema_9.t4
+            Table "regress_schema_9.t4"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ a      | integer |           |          | 
+ b      | integer |           |          | 
+Foreign-key constraints:
+    "fk" FOREIGN KEY (b) REFERENCES regress_schema_9.t6(b) MATCH FULL NOT ENFORCED
+    "fk1" FOREIGN KEY (a) REFERENCES regress_schema_9.t5(a) MATCH FULL DEFERRABLE NOT ENFORCED
+
 DROP SCHEMA regress_schema_2 CASCADE;
 NOTICE:  drop cascades to 2 other objects
 DETAIL:  drop cascades to type regress_schema_2.ss
@@ -313,5 +380,21 @@ drop cascades to type regress_schema_7.ss
 drop cascades to type regress_schema_7.sss
 drop cascades to type regress_schema_7.rainbow
 drop cascades to table regress_schema_7.t
+DROP SCHEMA regress_schema_8 CASCADE;
+NOTICE:  drop cascades to 6 other objects
+DETAIL:  drop cascades to table regress_schema_8.t2
+drop cascades to table regress_schema_8.t1
+drop cascades to table regress_schema_8.t3
+drop cascades to table regress_schema_8.t4
+drop cascades to table regress_schema_8.t5
+drop cascades to table regress_schema_8.t6
+DROP SCHEMA regress_schema_9 CASCADE;
+NOTICE:  drop cascades to 6 other objects
+DETAIL:  drop cascades to table regress_schema_9.t2
+drop cascades to table regress_schema_9.t1
+drop cascades to table regress_schema_9.t3
+drop cascades to table regress_schema_9.t4
+drop cascades to table regress_schema_9.t5
+drop cascades to table regress_schema_9.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 4a7fd2bc59a..213589952e3 100644
--- a/src/test/regress/expected/event_trigger.out
+++ b/src/test/regress/expected/event_trigger.out
@@ -426,10 +426,10 @@ 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=ALTER TABLE type=table identity=evttrig.two
 -- Partitioned tables with a partitioned index
 CREATE TABLE evttrig.parted (
     id int PRIMARY KEY)
diff --git a/src/test/regress/sql/create_schema.sql b/src/test/regress/sql/create_schema.sql
index 75fd928eacb..547016f54cd 100644
--- a/src/test/regress/sql/create_schema.sql
+++ b/src/test/regress/sql/create_schema.sql
@@ -168,12 +168,48 @@ CREATE SCHEMA regress_schema_7
   CREATE TABLE t(a floatrange, b ss, c rainbow);
 \dT regress_schema_7.*
 
+CREATE SCHEMA regress_schema_8
+    CREATE TABLE regress_schema_8.t2 (
+        b int,
+        a int REFERENCES t1 DEFERRABLE INITIALLY DEFERRED NOT ENFORCED
+              REFERENCES t3 DEFERRABLE INITIALLY DEFERRED NOT ENFORCED,
+        CONSTRAINT fk FOREIGN KEY (a) REFERENCES t1 DEFERRABLE INITIALLY DEFERRED NOT ENFORCED)
+    CREATE TABLE regress_schema_8.t1 (a int PRIMARY KEY)
+    CREATE TABLE t3 (a int PRIMARY KEY)
+    CREATE TABLE t4(b int,
+                    a int REFERENCES t5 DEFERRABLE INITIALLY DEFERRED NOT ENFORCED
+                          REFERENCES t6 DEFERRABLE INITIALLY DEFERRED NOT ENFORCED,
+        CONSTRAINT fk FOREIGN KEY (a) REFERENCES t6 DEFERRABLE INITIALLY DEFERRED NOT ENFORCED)
+    CREATE TABLE t5 (a int, b int, PRIMARY KEY (a))
+    CREATE TABLE t6 (a int, b int, PRIMARY KEY (a));
+
+\d regress_schema_8.t2
+\d regress_schema_8.t4
+
+CREATE SCHEMA regress_schema_9
+    CREATE TABLE t2 (a int, b int,
+        CONSTRAINT fk FOREIGN KEY (a, b) REFERENCES t1 MATCH FULL NOT DEFERRABLE INITIALLY IMMEDIATE NOT ENFORCED,
+        CONSTRAINT fk1 FOREIGN KEY (a, b) REFERENCES t3 MATCH FULL DEFERRABLE INITIALLY IMMEDIATE NOT ENFORCED)
+    CREATE TABLE t1 (a int, b int, PRIMARY KEY (b, a))
+    CREATE TABLE t3 (a int, b int, PRIMARY KEY (b, a))
+    CREATE TABLE t4 (a int, b int,
+        CONSTRAINT fk FOREIGN KEY (b) REFERENCES t6 MATCH FULL NOT DEFERRABLE INITIALLY IMMEDIATE NOT ENFORCED,
+        CONSTRAINT fk1 FOREIGN KEY (a) REFERENCES t5 MATCH FULL DEFERRABLE INITIALLY IMMEDIATE NOT ENFORCED)
+    CREATE TABLE t5 (a int, b int, PRIMARY KEY (a))
+    CREATE TABLE t6 (a int, b int, PRIMARY KEY (b));
+
+\d regress_schema_9.t2
+\d regress_schema_9.t4
+
+
 DROP SCHEMA regress_schema_2 CASCADE;
 DROP SCHEMA regress_schema_3 CASCADE;
 DROP SCHEMA regress_schema_4 CASCADE;
 DROP SCHEMA regress_schema_5 CASCADE;
 DROP SCHEMA regress_schema_6 CASCADE;
 DROP SCHEMA regress_schema_7 CASCADE;
+DROP SCHEMA regress_schema_8 CASCADE;
+DROP SCHEMA regress_schema_9 CASCADE;
 
 -- Clean up
 DROP ROLE regress_create_schema_role;
-- 
2.34.1



  [text/x-patch] v8-0002-CREATE-SCHEMA-CREATE-DOMAIN.patch (12.3K, 6-v8-0002-CREATE-SCHEMA-CREATE-DOMAIN.patch)
  download | inline diff:
From fc8c3f52c87d0fe057487a185d3735efd42eb456 Mon Sep 17 00:00:00 2001
From: jian he <[email protected]>
Date: Tue, 30 Dec 2025 11:48:49 +0800
Subject: [PATCH v8 2/5] CREATE SCHEMA CREATE DOMAIN

The SQL standard allows domains to be specified in a CREATE SCHEMA statement.
This adds support for that capability.

For example:
    CREATE SCHEMA schema_name AUTHORIZATION CURRENT_ROLE
    create domain ss as text not null;

The domain will be created within the to be created schema.  The domain name can
be schema-qualified or database-qualified, however it's not allowed to let
domain create within a different schema.

Author: Kirill Reshke <[email protected]>
Author: Jian He <[email protected]>
Reviewed-by: Alvaro Herrera <[email protected]>
Reviewed-by: Tom Lane <[email protected]>

Discussion: https://postgr.es/m/CALdSSPh4jUSDsWu3K58hjO60wnTRR0DuO4CKRcwa8EVuOSfXxg@mail.gmail.com
---
 doc/src/sgml/ref/create_schema.sgml           |  4 +-
 src/backend/parser/gram.y                     |  1 +
 src/backend/parser/parse_utilcmd.c            | 26 +++++++++
 src/bin/psql/tab-complete.in.c                | 12 ++--
 .../expected/create_schema.out                |  4 +-
 .../test_ddl_deparse/sql/create_schema.sql    |  3 +-
 src/test/regress/expected/create_schema.out   | 56 +++++++++++++++++++
 src/test/regress/sql/create_schema.sql        | 36 ++++++++++++
 8 files changed, 132 insertions(+), 10 deletions(-)

diff --git a/doc/src/sgml/ref/create_schema.sgml b/doc/src/sgml/ref/create_schema.sgml
index 625793a6b67..79186d2b936 100644
--- a/doc/src/sgml/ref/create_schema.sgml
+++ b/doc/src/sgml/ref/create_schema.sgml
@@ -100,8 +100,8 @@ CREATE SCHEMA IF NOT EXISTS AUTHORIZATION <replaceable class="parameter">role_sp
       <listitem>
        <para>
         An SQL statement defining an object to be created within the
-        schema. Currently, only <command>CREATE
-        TABLE</command>, <command>CREATE VIEW</command>, <command>CREATE
+        schema. Currently, only <command>CREATE DOMAIN</command>
+        <command>CREATE TABLE</command>, <command>CREATE VIEW</command>, <command>CREATE
         INDEX</command>, <command>CREATE SEQUENCE</command>, <command>CREATE
         TRIGGER</command> and <command>GRANT</command> are accepted as clauses
         within <command>CREATE SCHEMA</command>. Other kinds of objects may
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 28f4e11e30f..54e09d0ceb5 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -1637,6 +1637,7 @@ schema_stmt:
 			| CreateTrigStmt
 			| GrantStmt
 			| ViewStmt
+			| CreateDomainStmt
 		;
 
 
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 5d168fd0285..849a19c4e47 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -4458,6 +4458,32 @@ transformCreateSchemaStmtElements(ParseState *pstate, List *schemaElts,
 				elements = lappend(elements, element);
 				break;
 
+			case T_CreateDomainStmt:
+				{
+					char	   *domain_schema = NULL;
+
+					CreateDomainStmt *elp = castNode(CreateDomainStmt, element);
+
+					/*
+					 * The schema of the DOMAIN must match the schema being
+					 * created. If the domain name length exceeds 3, it will
+					 * fail in DeconstructQualifiedName.
+					 */
+					if (list_length(elp->domainname) == 2)
+						domain_schema = strVal(list_nth(elp->domainname, 0));
+					else if (list_length(elp->domainname) == 3)
+						domain_schema = strVal(list_nth(elp->domainname, 1));
+
+					if (domain_schema != NULL && strcmp(domain_schema, schemaName) != 0)
+						ereport(ERROR,
+								errcode(ERRCODE_INVALID_SCHEMA_DEFINITION),
+								errmsg("CREATE DOMAIN specifies a schema (%s) different from the one being created (%s)",
+									   domain_schema, schemaName));
+
+					elements = lappend(elements, element);
+				}
+				break;
+
 			default:
 				elog(ERROR, "unrecognized node type: %d",
 					 (int) nodeTag(element));
diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c
index 75a101c6ab5..e750b2dbb87 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -2201,7 +2201,7 @@ match_previous_words(int pattern_id,
 	{
 		/* only some object types can be created as part of CREATE SCHEMA */
 		if (HeadMatches("CREATE", "SCHEMA"))
-			COMPLETE_WITH("TABLE", "VIEW", "INDEX", "SEQUENCE", "TRIGGER",
+			COMPLETE_WITH("TABLE", "VIEW", "INDEX", "SEQUENCE", "TRIGGER", "DOMAIN",
 			/* for INDEX and TABLE/SEQUENCE, respectively */
 						  "UNIQUE", "UNLOGGED");
 		else
@@ -3471,15 +3471,15 @@ match_previous_words(int pattern_id,
 	else if (Matches("CREATE", "DATABASE", MatchAny, "STRATEGY"))
 		COMPLETE_WITH("WAL_LOG", "FILE_COPY");
 
-	/* CREATE DOMAIN */
-	else if (Matches("CREATE", "DOMAIN", MatchAny))
+	/* CREATE DOMAIN --- is allowed inside CREATE SCHEMA, so use TailMatches */
+	else if (TailMatches("CREATE", "DOMAIN", MatchAny))
 		COMPLETE_WITH("AS");
-	else if (Matches("CREATE", "DOMAIN", MatchAny, "AS"))
+	else if (TailMatches("CREATE", "DOMAIN", MatchAny, "AS"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes);
-	else if (Matches("CREATE", "DOMAIN", MatchAny, "AS", MatchAny))
+	else if (TailMatches("CREATE", "DOMAIN", MatchAny, "AS", MatchAny))
 		COMPLETE_WITH("COLLATE", "DEFAULT", "CONSTRAINT",
 					  "NOT NULL", "NULL", "CHECK (");
-	else if (Matches("CREATE", "DOMAIN", MatchAny, "COLLATE"))
+	else if (TailMatches("CREATE", "DOMAIN", MatchAny, "COLLATE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_collations);
 
 	/* CREATE EXTENSION */
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..d73c4702051 100644
--- a/src/test/modules/test_ddl_deparse/expected/create_schema.out
+++ b/src/test/modules/test_ddl_deparse/expected/create_schema.out
@@ -13,7 +13,9 @@ CREATE SCHEMA IF NOT EXISTS baz;
 NOTICE:  schema "baz" already exists, skipping
 CREATE SCHEMA element_test
   CREATE TABLE foo (id int)
-  CREATE VIEW bar AS SELECT * FROM foo;
+  CREATE VIEW bar AS SELECT * FROM foo
+  CREATE DOMAIN d1 AS INT;
 NOTICE:  DDL test: type simple, tag CREATE SCHEMA
 NOTICE:  DDL test: type simple, tag CREATE TABLE
 NOTICE:  DDL test: type simple, tag CREATE VIEW
+NOTICE:  DDL test: type simple, tag CREATE DOMAIN
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..57ada462070 100644
--- a/src/test/modules/test_ddl_deparse/sql/create_schema.sql
+++ b/src/test/modules/test_ddl_deparse/sql/create_schema.sql
@@ -14,4 +14,5 @@ CREATE SCHEMA IF NOT EXISTS baz;
 
 CREATE SCHEMA element_test
   CREATE TABLE foo (id int)
-  CREATE VIEW bar AS SELECT * FROM foo;
+  CREATE VIEW bar AS SELECT * FROM foo
+  CREATE DOMAIN d1 AS INT;
diff --git a/src/test/regress/expected/create_schema.out b/src/test/regress/expected/create_schema.out
index 38530c282a9..0533c29a311 100644
--- a/src/test/regress/expected/create_schema.out
+++ b/src/test/regress/expected/create_schema.out
@@ -131,5 +131,61 @@ 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;
+-- Cases where the schema creation with domain.
+--fail. cannot CREATE DOMAIN to other schema
+CREATE SCHEMA regress_schema_2 AUTHORIZATION CURRENT_ROLE
+  CREATE DOMAIN public.ss AS TEXT NOT NULL DEFAULT 'hello' CONSTRAINT nn CHECK (VALUE <> 'hello')
+  CREATE TABLE t(a ss);
+ERROR:  CREATE DOMAIN specifies a schema (public) different from the one being created (regress_schema_2)
+--fail. cannot CREATE DOMAIN to other schema
+CREATE SCHEMA regress_schema_2 AUTHORIZATION CURRENT_ROLE
+  CREATE DOMAIN postgres.public.ss AS TEXT NOT NULL CONSTRAINT nn CHECK (VALUE <> 'hello') DEFAULT 'hello'
+  CREATE TABLE t(a ss);
+ERROR:  CREATE DOMAIN specifies a schema (public) different from the one being created (regress_schema_2)
+--fail. improper qualified name
+CREATE SCHEMA regress_schema_2 CREATE DOMAIN ss.postgres.regress_schema_2.ss AS TEXT;
+ERROR:  improper qualified name (too many dotted names): ss.postgres.regress_schema_2.ss
+--fail. Execute subcommands in order; we do not implicitly reorder them.
+CREATE SCHEMA regress_schema_2 AUTHORIZATION CURRENT_ROLE
+  CREATE DOMAIN ss1 AS ss
+  CREATE DOMAIN ss AS TEXT;
+ERROR:  type "ss" does not exist
+LINE 2:   CREATE DOMAIN ss1 AS ss
+                               ^
+--ok, qualified schema name for domain should be same as the created schema.
+CREATE SCHEMA regress_schema_2 AUTHORIZATION CURRENT_ROLE
+  CREATE DOMAIN regress_schema_2.ss AS TEXT NOT NULL CONSTRAINT nn CHECK (VALUE <> 'hello') DEFAULT 'hello' COLLATE "C"
+  CREATE TABLE t(a regress_schema_2.ss);
+\dD regress_schema_2.*
+                                            List of domains
+      Schema      | Name | Type | Collation | Nullable |    Default    |             Check              
+------------------+------+------+-----------+----------+---------------+--------------------------------
+ regress_schema_2 | ss   | text | C         | not null | 'hello'::text | CHECK (VALUE <> 'hello'::text)
+(1 row)
+
+--ok, no qualified schema name for domain.
+CREATE SCHEMA regress_schema_3 AUTHORIZATION CURRENT_ROLE
+  CREATE DOMAIN ss AS TEXT CONSTRAINT nn CHECK (VALUE <> 'hello') NOT NULL DEFAULT 'hello'
+  CREATE DOMAIN ss1 AS ss
+  CREATE VIEW test AS SELECT 'hello'::ss AS test
+  CREATE TABLE t(a ss1);
+\dD regress_schema_3.*
+                                                    List of domains
+      Schema      | Name |        Type         | Collation | Nullable |    Default    |             Check              
+------------------+------+---------------------+-----------+----------+---------------+--------------------------------
+ regress_schema_3 | ss   | text                |           | not null | 'hello'::text | CHECK (VALUE <> 'hello'::text)
+ regress_schema_3 | ss1  | regress_schema_3.ss |           |          | 'hello'::text | 
+(2 rows)
+
+DROP SCHEMA regress_schema_2 CASCADE;
+NOTICE:  drop cascades to 2 other objects
+DETAIL:  drop cascades to type regress_schema_2.ss
+drop cascades to table regress_schema_2.t
+DROP SCHEMA regress_schema_3 CASCADE;
+NOTICE:  drop cascades to 4 other objects
+DETAIL:  drop cascades to type regress_schema_3.ss
+drop cascades to type regress_schema_3.ss1
+drop cascades to view regress_schema_3.test
+drop cascades to table regress_schema_3.t
 -- Clean up
 DROP ROLE regress_create_schema_role;
diff --git a/src/test/regress/sql/create_schema.sql b/src/test/regress/sql/create_schema.sql
index b3dc1cfd758..54a07054767 100644
--- a/src/test/regress/sql/create_schema.sql
+++ b/src/test/regress/sql/create_schema.sql
@@ -71,5 +71,41 @@ CREATE SCHEMA regress_schema_1 AUTHORIZATION CURRENT_ROLE
 DROP SCHEMA regress_schema_1 CASCADE;
 RESET ROLE;
 
+-- Cases where the schema creation with domain.
+--fail. cannot CREATE DOMAIN to other schema
+CREATE SCHEMA regress_schema_2 AUTHORIZATION CURRENT_ROLE
+  CREATE DOMAIN public.ss AS TEXT NOT NULL DEFAULT 'hello' CONSTRAINT nn CHECK (VALUE <> 'hello')
+  CREATE TABLE t(a ss);
+
+--fail. cannot CREATE DOMAIN to other schema
+CREATE SCHEMA regress_schema_2 AUTHORIZATION CURRENT_ROLE
+  CREATE DOMAIN postgres.public.ss AS TEXT NOT NULL CONSTRAINT nn CHECK (VALUE <> 'hello') DEFAULT 'hello'
+  CREATE TABLE t(a ss);
+
+--fail. improper qualified name
+CREATE SCHEMA regress_schema_2 CREATE DOMAIN ss.postgres.regress_schema_2.ss AS TEXT;
+
+--fail. Execute subcommands in order; we do not implicitly reorder them.
+CREATE SCHEMA regress_schema_2 AUTHORIZATION CURRENT_ROLE
+  CREATE DOMAIN ss1 AS ss
+  CREATE DOMAIN ss AS TEXT;
+
+--ok, qualified schema name for domain should be same as the created schema.
+CREATE SCHEMA regress_schema_2 AUTHORIZATION CURRENT_ROLE
+  CREATE DOMAIN regress_schema_2.ss AS TEXT NOT NULL CONSTRAINT nn CHECK (VALUE <> 'hello') DEFAULT 'hello' COLLATE "C"
+  CREATE TABLE t(a regress_schema_2.ss);
+\dD regress_schema_2.*
+
+--ok, no qualified schema name for domain.
+CREATE SCHEMA regress_schema_3 AUTHORIZATION CURRENT_ROLE
+  CREATE DOMAIN ss AS TEXT CONSTRAINT nn CHECK (VALUE <> 'hello') NOT NULL DEFAULT 'hello'
+  CREATE DOMAIN ss1 AS ss
+  CREATE VIEW test AS SELECT 'hello'::ss AS test
+  CREATE TABLE t(a ss1);
+\dD regress_schema_3.*
+
+DROP SCHEMA regress_schema_2 CASCADE;
+DROP SCHEMA regress_schema_3 CASCADE;
+
 -- Clean up
 DROP ROLE regress_create_schema_role;
-- 
2.34.1



^ permalink  raw  reply  [nested|flat] 32+ messages in thread

* Re: CREATE SCHEMA ... CREATE DOMAIN support
  2024-11-30 19:08 Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  2024-12-03 19:35 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Peter Eisentraut <[email protected]>
  2024-12-03 20:07   ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  2024-12-11 06:22     ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <[email protected]>
  2024-12-11 17:08       ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  2025-08-19 03:36         ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <[email protected]>
  2025-08-22 08:59           ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <[email protected]>
  2025-09-02 21:24             ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  2025-12-17 13:29               ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <[email protected]>
  2025-12-31 01:40                 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <[email protected]>
@ 2026-01-19 15:06                   ` jian he <[email protected]>
  2026-03-10 14:11                     ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <[email protected]>
  0 siblings, 1 reply; 32+ messages in thread

From: jian he @ 2026-01-19 15:06 UTC (permalink / raw)
  To: Tom Lane <[email protected]>; +Cc: Kirill Reshke <[email protected]>; Peter Eisentraut <[email protected]>; PostgreSQL Hackers <[email protected]>

hi.

https://api.cirrus-ci.com/v1/artifact/task/6697254683148288/testrun/build/testrun/regress/regress/re...
seems there is some concurrency issue in regress test.

I made minor adjustments to the regression tests and updated the comments;
otherwise, this is unchanged from v8.


--
jian
https://www.enterprisedb.com


Attachments:

  [text/x-patch] v9-0005-CREATE-SCHEMA-foreign-key-executed-at-the-end.patch (21.2K, 2-v9-0005-CREATE-SCHEMA-foreign-key-executed-at-the-end.patch)
  download | inline diff:
From f7dc6dc0b21d9658de4b7d2a7c725d405b9d2a5b Mon Sep 17 00:00:00 2001
From: jian he <[email protected]>
Date: Mon, 19 Jan 2026 23:02:53 +0800
Subject: [PATCH v9 5/5] CREATE SCHEMA foreign key executed at the end

For CREATE SCHEMA, all subcommands are executed in the order they are specified.
However, to support foreign key cross referencing, we need transform FOREIGN KEY
clause into ALTER TABLE ... ADD FOREIGN KEY command and append them to the end
of the CREATE SCHEMA execution sequence.

All CREATE TABLE commands are executed first, so subsequent ALTER TABLE ... ADD
FOREIGN KEY statements will not encounter any conflicts.

Discussion: https://postgr.es/m/[email protected]
---
 src/backend/commands/schemacmds.c           | 136 ++++++++++++++++++++
 src/backend/parser/parse_utilcmd.c          |  36 +++---
 src/include/parser/parse_utilcmd.h          |   1 +
 src/test/regress/expected/create_schema.out |  83 ++++++++++++
 src/test/regress/expected/event_trigger.out |   2 +-
 src/test/regress/sql/create_schema.sql      |  36 ++++++
 6 files changed, 274 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/schemacmds.c b/src/backend/commands/schemacmds.c
index 09928c58d9d..cdc15aaea04 100644
--- a/src/backend/commands/schemacmds.c
+++ b/src/backend/commands/schemacmds.c
@@ -198,6 +198,142 @@ CreateSchemaCommand(ParseState *pstate, CreateSchemaStmt *stmt,
 													   stmt->schemaElts,
 													   schemaName);
 
+	/*
+	 * Reorder the list of commands embedded in the CREATE SCHEMA statement.
+	 * We only do this for foreign keys. Foreign key are transformed into
+	 * ALTER TABLE ... ADD FOREIGN KEY commands and moved to the end of the
+	 * CREATE SCHEMA to-do list (parsetree_list).
+	 *
+	 * At the same time, the original foreign key Constraint nodes will be
+	 * removed from the CreateStmt, since they have already been converted
+	 * into AlterTableCmds. No parse analysis is required for the original
+	 * foreign key constraint nodes: since expressions are not allowed in
+	 * foreign key constraints.
+	 *
+	 * See also transformFKConstraints and transformConstraintAttrs.
+	 */
+	foreach(parsetree_item, parsetree_list)
+	{
+		ListCell   *elements;
+		CreateStmt *csstmt;
+
+		Node	   *stmt = (Node *) lfirst(parsetree_item);
+
+		if (!IsA(stmt, CreateStmt))
+			continue;
+
+		csstmt = castNode(CreateStmt, stmt);
+
+		foreach(elements, csstmt->tableElts)
+		{
+			AlterTableStmt *alterstmt;
+			AlterTableCmd *altercmd;
+
+			Node	   *element = lfirst(elements);
+
+			if (IsA(element, Constraint))
+			{
+				Constraint *constr = castNode(Constraint, element);
+
+				if (constr->contype != CONSTR_FOREIGN)
+					continue;
+
+				alterstmt = makeNode(AlterTableStmt);
+				alterstmt->relation = copyObject(csstmt->relation);
+				alterstmt->cmds = NIL;
+				alterstmt->objtype = OBJECT_TABLE;
+
+				altercmd = makeNode(AlterTableCmd);
+				altercmd->subtype = AT_AddConstraint;
+				altercmd->name = NULL;
+				altercmd->def = (Node *) copyObject(constr);
+
+				alterstmt->cmds = lappend(alterstmt->cmds, altercmd);
+
+				/*
+				 * The foreign key has already been transformed into an
+				 * AlterTableCmd; remove the original entry from
+				 * CreateStmt->tableElts.
+				 */
+				csstmt->tableElts = foreach_delete_current(csstmt->tableElts,
+														   elements);
+
+				parsetree_list = lappend(parsetree_list, alterstmt);
+
+				continue;
+			}
+			else if (IsA(element, ColumnDef))
+			{
+				ColumnDef  *entry = castNode(ColumnDef, element);
+
+				transformConstraintAttrs(pstate, entry->constraints);
+
+				for (int constrpos = 0; constrpos < list_length(entry->constraints); constrpos++)
+				{
+					Constraint *colconstr = list_nth_node(Constraint,
+														  entry->constraints,
+														  constrpos);
+
+					if (colconstr->contype != CONSTR_FOREIGN)
+						continue;
+
+					alterstmt = makeNode(AlterTableStmt);
+					altercmd = makeNode(AlterTableCmd);
+
+					colconstr->fk_attrs = list_make1(makeString(entry->colname));
+
+					alterstmt->relation = copyObject(csstmt->relation);
+					alterstmt->cmds = NIL;
+					alterstmt->objtype = OBJECT_TABLE;
+
+					altercmd->subtype = AT_AddConstraint;
+					altercmd->name = NULL;
+					altercmd->def = (Node *) copyObject(colconstr);
+					alterstmt->cmds = lappend(alterstmt->cmds, altercmd);
+
+					parsetree_list = lappend(parsetree_list, alterstmt);
+
+					/*
+					 * Column constraints separate the Constraint node from
+					 * its attributes; a full column-level foreign key
+					 * constraint may be represented by multiple Constraint
+					 * nodes. After transformConstraintAttrs, the main foreign
+					 * key Constraint node already contains all required
+					 * information. Therefore, when the foreign key Constraint
+					 * node is removed, the associated attribute nodes (which
+					 * are also Constraint nodes) must be removed as well.
+					 */
+					for (int restpos = constrpos + 1; restpos < list_length(entry->constraints);)
+					{
+						Constraint *nextcolconstr = list_nth_node(Constraint,
+																  entry->constraints,
+																  restpos);
+
+						if (nextcolconstr->contype == CONSTR_ATTR_DEFERRABLE ||
+							nextcolconstr->contype == CONSTR_ATTR_NOT_DEFERRABLE ||
+							nextcolconstr->contype == CONSTR_ATTR_DEFERRED ||
+							nextcolconstr->contype == CONSTR_ATTR_IMMEDIATE ||
+							nextcolconstr->contype == CONSTR_ATTR_NOT_ENFORCED)
+						{
+							entry->constraints = list_delete_nth_cell(entry->constraints,
+																	  restpos);
+						}
+						else
+							break;
+					}
+
+					entry->constraints = list_delete_nth_cell(entry->constraints, constrpos);
+
+					/*
+					 * Since we have deleted the foreign key Constraint node,
+					 * update constrpos too.
+					 */
+					constrpos = constrpos - 1;
+				}
+			}
+		}
+	}
+
 	/*
 	 * Execute each command contained in the CREATE SCHEMA.  Since the grammar
 	 * allows only utility commands in CREATE SCHEMA, there is no need to pass
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index c2a8afedb3e..4363df2afaf 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -122,8 +122,6 @@ static void transformFKConstraints(CreateStmtContext *cxt,
 								   bool isAddConstraint);
 static void transformCheckConstraints(CreateStmtContext *cxt,
 									  bool skipValidation);
-static void transformConstraintAttrs(CreateStmtContext *cxt,
-									 List *constraintList);
 static void transformColumnType(CreateStmtContext *cxt, ColumnDef *column);
 static void checkSchemaName(ParseState *pstate, const char *context_schema,
 							RangeVar *relation);
@@ -693,7 +691,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
@@ -4189,8 +4187,8 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
  * EXCLUSION, and PRIMARY KEY constraints, but someday they ought to be
  * supported for other constraint types.
  */
-static void
-transformConstraintAttrs(CreateStmtContext *cxt, List *constraintList)
+void
+transformConstraintAttrs(ParseState *pstate, List *constraintList)
 {
 	Constraint *lastprimarycon = NULL;
 	bool		saw_deferrability = false;
@@ -4219,12 +4217,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;
@@ -4234,12 +4232,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 &&
@@ -4247,7 +4245,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:
@@ -4255,12 +4253,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;
 
@@ -4273,7 +4271,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:
@@ -4281,12 +4279,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;
@@ -4298,12 +4296,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;
@@ -4315,12 +4313,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;
 
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index d151bba03eb..cf7bb3a5731 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -20,6 +20,7 @@ typedef struct AttrMap AttrMap; /* avoid including attmap.h here */
 
 
 extern List *transformCreateStmt(CreateStmt *stmt, const char *queryString);
+extern void transformConstraintAttrs(ParseState *pstate, List *constraintList);
 extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   const char *queryString,
 											   List **beforeStmts,
diff --git a/src/test/regress/expected/create_schema.out b/src/test/regress/expected/create_schema.out
index a33cbd0c0d9..6df41f9a2f3 100644
--- a/src/test/regress/expected/create_schema.out
+++ b/src/test/regress/expected/create_schema.out
@@ -282,6 +282,73 @@ CREATE SCHEMA regress_schema_7
  regress_schema_7 | regress_schema_7.sss             | 
 (5 rows)
 
+CREATE SCHEMA regress_schema_8
+    CREATE TABLE regress_schema_8.t2 (
+        b int,
+        a int REFERENCES t1 DEFERRABLE INITIALLY DEFERRED NOT ENFORCED
+              REFERENCES t3 DEFERRABLE INITIALLY DEFERRED NOT ENFORCED,
+        CONSTRAINT fk FOREIGN KEY (a) REFERENCES t1 DEFERRABLE INITIALLY DEFERRED NOT ENFORCED)
+    CREATE TABLE regress_schema_8.t1 (a int PRIMARY KEY)
+    CREATE TABLE t3 (a int PRIMARY KEY)
+    CREATE TABLE t4(b int,
+                    a int REFERENCES t5 DEFERRABLE INITIALLY DEFERRED NOT ENFORCED
+                          REFERENCES t6 DEFERRABLE INITIALLY DEFERRED NOT ENFORCED,
+        CONSTRAINT fk FOREIGN KEY (a) REFERENCES t6 DEFERRABLE INITIALLY DEFERRED NOT ENFORCED)
+    CREATE TABLE t5 (a int, b int, PRIMARY KEY (a))
+    CREATE TABLE t6 (a int, b int, PRIMARY KEY (a));
+\d regress_schema_8.t2
+            Table "regress_schema_8.t2"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ b      | integer |           |          | 
+ a      | integer |           |          | 
+Foreign-key constraints:
+    "fk" FOREIGN KEY (a) REFERENCES regress_schema_8.t1(a) DEFERRABLE INITIALLY DEFERRED NOT ENFORCED
+    "t2_a_fkey" FOREIGN KEY (a) REFERENCES regress_schema_8.t1(a) DEFERRABLE INITIALLY DEFERRED NOT ENFORCED
+    "t2_a_fkey1" FOREIGN KEY (a) REFERENCES regress_schema_8.t3(a) DEFERRABLE INITIALLY DEFERRED NOT ENFORCED
+
+\d regress_schema_8.t4
+            Table "regress_schema_8.t4"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ b      | integer |           |          | 
+ a      | integer |           |          | 
+Foreign-key constraints:
+    "fk" FOREIGN KEY (a) REFERENCES regress_schema_8.t6(a) DEFERRABLE INITIALLY DEFERRED NOT ENFORCED
+    "t4_a_fkey" FOREIGN KEY (a) REFERENCES regress_schema_8.t5(a) DEFERRABLE INITIALLY DEFERRED NOT ENFORCED
+    "t4_a_fkey1" FOREIGN KEY (a) REFERENCES regress_schema_8.t6(a) DEFERRABLE INITIALLY DEFERRED NOT ENFORCED
+
+CREATE SCHEMA regress_schema_9
+    CREATE TABLE t2 (a int, b int,
+        CONSTRAINT fk FOREIGN KEY (a, b) REFERENCES t1 MATCH FULL NOT DEFERRABLE INITIALLY IMMEDIATE NOT ENFORCED,
+        CONSTRAINT fk1 FOREIGN KEY (a, b) REFERENCES t3 MATCH FULL DEFERRABLE INITIALLY IMMEDIATE NOT ENFORCED)
+    CREATE TABLE t1 (a int, b int, PRIMARY KEY (b, a))
+    CREATE TABLE t3 (a int, b int, PRIMARY KEY (b, a))
+    CREATE TABLE t4 (a int, b int,
+        CONSTRAINT fk FOREIGN KEY (b) REFERENCES t6 MATCH FULL NOT DEFERRABLE INITIALLY IMMEDIATE NOT ENFORCED,
+        CONSTRAINT fk1 FOREIGN KEY (a) REFERENCES t5 MATCH FULL DEFERRABLE INITIALLY IMMEDIATE NOT ENFORCED)
+    CREATE TABLE t5 (a int, b int, PRIMARY KEY (a))
+    CREATE TABLE t6 (a int, b int, PRIMARY KEY (b));
+\d regress_schema_9.t2
+            Table "regress_schema_9.t2"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ a      | integer |           |          | 
+ b      | integer |           |          | 
+Foreign-key constraints:
+    "fk" FOREIGN KEY (a, b) REFERENCES regress_schema_9.t1(b, a) MATCH FULL NOT ENFORCED
+    "fk1" FOREIGN KEY (a, b) REFERENCES regress_schema_9.t3(b, a) MATCH FULL DEFERRABLE NOT ENFORCED
+
+\d regress_schema_9.t4
+            Table "regress_schema_9.t4"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ a      | integer |           |          | 
+ b      | integer |           |          | 
+Foreign-key constraints:
+    "fk" FOREIGN KEY (b) REFERENCES regress_schema_9.t6(b) MATCH FULL NOT ENFORCED
+    "fk1" FOREIGN KEY (a) REFERENCES regress_schema_9.t5(a) MATCH FULL DEFERRABLE NOT ENFORCED
+
 DROP SCHEMA regress_schema_2 CASCADE;
 NOTICE:  drop cascades to 2 other objects
 DETAIL:  drop cascades to type regress_schema_2.ss
@@ -313,5 +380,21 @@ drop cascades to type regress_schema_7.ss
 drop cascades to type regress_schema_7.sss
 drop cascades to type regress_schema_7.rainbow
 drop cascades to table regress_schema_7.t
+DROP SCHEMA regress_schema_8 CASCADE;
+NOTICE:  drop cascades to 6 other objects
+DETAIL:  drop cascades to table regress_schema_8.t2
+drop cascades to table regress_schema_8.t1
+drop cascades to table regress_schema_8.t3
+drop cascades to table regress_schema_8.t4
+drop cascades to table regress_schema_8.t5
+drop cascades to table regress_schema_8.t6
+DROP SCHEMA regress_schema_9 CASCADE;
+NOTICE:  drop cascades to 6 other objects
+DETAIL:  drop cascades to table regress_schema_9.t2
+drop cascades to table regress_schema_9.t1
+drop cascades to table regress_schema_9.t3
+drop cascades to table regress_schema_9.t4
+drop cascades to table regress_schema_9.t5
+drop cascades to table regress_schema_9.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 4a7fd2bc59a..213589952e3 100644
--- a/src/test/regress/expected/event_trigger.out
+++ b/src/test/regress/expected/event_trigger.out
@@ -426,10 +426,10 @@ 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=ALTER TABLE type=table identity=evttrig.two
 -- Partitioned tables with a partitioned index
 CREATE TABLE evttrig.parted (
     id int PRIMARY KEY)
diff --git a/src/test/regress/sql/create_schema.sql b/src/test/regress/sql/create_schema.sql
index 75fd928eacb..547016f54cd 100644
--- a/src/test/regress/sql/create_schema.sql
+++ b/src/test/regress/sql/create_schema.sql
@@ -168,12 +168,48 @@ CREATE SCHEMA regress_schema_7
   CREATE TABLE t(a floatrange, b ss, c rainbow);
 \dT regress_schema_7.*
 
+CREATE SCHEMA regress_schema_8
+    CREATE TABLE regress_schema_8.t2 (
+        b int,
+        a int REFERENCES t1 DEFERRABLE INITIALLY DEFERRED NOT ENFORCED
+              REFERENCES t3 DEFERRABLE INITIALLY DEFERRED NOT ENFORCED,
+        CONSTRAINT fk FOREIGN KEY (a) REFERENCES t1 DEFERRABLE INITIALLY DEFERRED NOT ENFORCED)
+    CREATE TABLE regress_schema_8.t1 (a int PRIMARY KEY)
+    CREATE TABLE t3 (a int PRIMARY KEY)
+    CREATE TABLE t4(b int,
+                    a int REFERENCES t5 DEFERRABLE INITIALLY DEFERRED NOT ENFORCED
+                          REFERENCES t6 DEFERRABLE INITIALLY DEFERRED NOT ENFORCED,
+        CONSTRAINT fk FOREIGN KEY (a) REFERENCES t6 DEFERRABLE INITIALLY DEFERRED NOT ENFORCED)
+    CREATE TABLE t5 (a int, b int, PRIMARY KEY (a))
+    CREATE TABLE t6 (a int, b int, PRIMARY KEY (a));
+
+\d regress_schema_8.t2
+\d regress_schema_8.t4
+
+CREATE SCHEMA regress_schema_9
+    CREATE TABLE t2 (a int, b int,
+        CONSTRAINT fk FOREIGN KEY (a, b) REFERENCES t1 MATCH FULL NOT DEFERRABLE INITIALLY IMMEDIATE NOT ENFORCED,
+        CONSTRAINT fk1 FOREIGN KEY (a, b) REFERENCES t3 MATCH FULL DEFERRABLE INITIALLY IMMEDIATE NOT ENFORCED)
+    CREATE TABLE t1 (a int, b int, PRIMARY KEY (b, a))
+    CREATE TABLE t3 (a int, b int, PRIMARY KEY (b, a))
+    CREATE TABLE t4 (a int, b int,
+        CONSTRAINT fk FOREIGN KEY (b) REFERENCES t6 MATCH FULL NOT DEFERRABLE INITIALLY IMMEDIATE NOT ENFORCED,
+        CONSTRAINT fk1 FOREIGN KEY (a) REFERENCES t5 MATCH FULL DEFERRABLE INITIALLY IMMEDIATE NOT ENFORCED)
+    CREATE TABLE t5 (a int, b int, PRIMARY KEY (a))
+    CREATE TABLE t6 (a int, b int, PRIMARY KEY (b));
+
+\d regress_schema_9.t2
+\d regress_schema_9.t4
+
+
 DROP SCHEMA regress_schema_2 CASCADE;
 DROP SCHEMA regress_schema_3 CASCADE;
 DROP SCHEMA regress_schema_4 CASCADE;
 DROP SCHEMA regress_schema_5 CASCADE;
 DROP SCHEMA regress_schema_6 CASCADE;
 DROP SCHEMA regress_schema_7 CASCADE;
+DROP SCHEMA regress_schema_8 CASCADE;
+DROP SCHEMA regress_schema_9 CASCADE;
 
 -- Clean up
 DROP ROLE regress_create_schema_role;
-- 
2.34.1



  [text/x-patch] v9-0004-CREATE-SCHEMA-CREATE-TYPE.patch (15.4K, 3-v9-0004-CREATE-SCHEMA-CREATE-TYPE.patch)
  download | inline diff:
From a526fb90046ee1c6087961e45607e5406f7fc7b6 Mon Sep 17 00:00:00 2001
From: jian he <[email protected]>
Date: Mon, 19 Jan 2026 21:39:35 +0800
Subject: [PATCH v9 4/5] CREATE SCHEMA CREATE TYPE

The SQL standard allows types to be specified in a CREATE SCHEMA statement.
This adds support for that capability.

For example:
CREATE SCHEMA schema_name CREATE TYPE ss;

The type will be created within the to be created schema.  The type name can be
schema-qualified or database-qualified, however it's not allowed to let type
create within a different schema.

Discussion: https://postgr.es/m/CALdSSPh4jUSDsWu3K58hjO60wnTRR0DuO4CKRcwa8EVuOSfXxg@mail.gmail.com
---
 doc/src/sgml/ref/create_schema.sgml           |  3 +-
 src/backend/parser/parse_utilcmd.c            | 52 ++++++++++--
 .../expected/create_schema.out                | 10 ++-
 .../test_ddl_deparse/sql/create_schema.sql    |  6 +-
 src/test/regress/expected/create_schema.out   | 83 +++++++++++++++++++
 src/test/regress/expected/create_type.out     | 12 +++
 src/test/regress/sql/create_schema.sql        | 39 +++++++++
 src/test/regress/sql/create_type.sql          | 12 +++
 8 files changed, 206 insertions(+), 11 deletions(-)

diff --git a/doc/src/sgml/ref/create_schema.sgml b/doc/src/sgml/ref/create_schema.sgml
index d8273bb2d0c..905e966e30e 100644
--- a/doc/src/sgml/ref/create_schema.sgml
+++ b/doc/src/sgml/ref/create_schema.sgml
@@ -104,7 +104,8 @@ CREATE SCHEMA IF NOT EXISTS AUTHORIZATION <replaceable class="parameter">role_sp
         <command>CREATE DOMAIN</command>
         <command>CREATE TABLE</command>, <command>CREATE VIEW</command>, <command>CREATE
         INDEX</command>, <command>CREATE SEQUENCE</command>, <command>CREATE
-        TRIGGER</command> and <command>GRANT</command> are accepted as clauses
+        TRIGGER</command>, <command>CREATE TYPE</command> and
+        <command>GRANT</command> are accepted as clauses
         within <command>CREATE SCHEMA</command>. Other kinds of objects may
         be created in separate commands after the schema is created.
        </para>
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index af164e360b5..c2a8afedb3e 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -4496,7 +4496,7 @@ transformCreateSchemaStmtElements(ParseState *pstate, List *schemaElts,
 					obj_type = asc_toupper(stringify_objtype(stmt->kind),
 										   strlen(stringify_objtype(stmt->kind)));
 
-					if (stmt->kind != OBJECT_COLLATION)
+					if (stmt->kind != OBJECT_COLLATION && stmt->kind != OBJECT_TYPE)
 						ereport(ERROR,
 								errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 								errmsg("CREATE SCHEMA ... CREATE %s currently not supported", obj_type));
@@ -4513,16 +4513,52 @@ transformCreateSchemaStmtElements(ParseState *pstate, List *schemaElts,
 				}
 				break;
 
-				/*
-				 * gram.y classifies these as DefineStmt as well; therefore,
-				 * we must explicitly raise an error for these cases.
-				 */
 			case T_CompositeTypeStmt:
+				{
+					CompositeTypeStmt *stmt = castNode(CompositeTypeStmt, element);
+
+					checkSchemaName(pstate, schemaName, stmt->typevar);
+
+					elements = lappend(elements, element);
+				}
+				break;
+
 			case T_CreateEnumStmt:
+				{
+					char	   *typschema = NULL;
+					char	   *typname;
+
+					CreateEnumStmt *stmt = castNode(CreateEnumStmt, element);
+
+					DeconstructQualifiedName(stmt->typeName, &typschema, &typname);
+
+					if (typschema && strcmp(typschema, schemaName) != 0)
+						ereport(ERROR,
+								errcode(ERRCODE_INVALID_SCHEMA_DEFINITION),
+								errmsg("CREATE TYPE specifies a schema (%s) different from the one being created (%s)",
+									   typschema, schemaName));
+
+					elements = lappend(elements, element);
+				}
+				break;
+
 			case T_CreateRangeStmt:
-				ereport(ERROR,
-						errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-						errmsg("CREATE SCHEMA ... CREATE TYPE currently not supported"));
+				{
+					char	   *typschema = NULL;
+					char	   *typname;
+
+					CreateRangeStmt *stmt = castNode(CreateRangeStmt, element);
+
+					DeconstructQualifiedName(stmt->typeName, &typschema, &typname);
+
+					if (typschema && strcmp(typschema, schemaName) != 0)
+						ereport(ERROR,
+								errcode(ERRCODE_INVALID_SCHEMA_DEFINITION),
+								errmsg("CREATE TYPE specifies a schema (%s) different from the one being created (%s)",
+									   schemaName, typschema));
+
+					elements = lappend(elements, element);
+				}
 				break;
 			default:
 				elog(ERROR, "unrecognized node type: %d",
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 2a905b28600..b2c85682b20 100644
--- a/src/test/modules/test_ddl_deparse/expected/create_schema.out
+++ b/src/test/modules/test_ddl_deparse/expected/create_schema.out
@@ -15,9 +15,17 @@ CREATE SCHEMA element_test
   CREATE TABLE foo (id int)
   CREATE VIEW bar AS SELECT * FROM foo
   CREATE DOMAIN d1 AS INT
-  CREATE COLLATION coll (LOCALE="C");
+  CREATE COLLATION coll (LOCALE="C")
+  CREATE TYPE floatrange AS RANGE (subtype = float8, subtype_diff = float8mi)
+  CREATE TYPE ss AS (a int)
+  CREATE TYPE sss
+  CREATE TYPE rainbow AS ENUM ('red', 'orange');
 NOTICE:  DDL test: type simple, tag CREATE SCHEMA
 NOTICE:  DDL test: type simple, tag CREATE TABLE
 NOTICE:  DDL test: type simple, tag CREATE VIEW
 NOTICE:  DDL test: type simple, tag CREATE DOMAIN
 NOTICE:  DDL test: type simple, tag CREATE COLLATION
+NOTICE:  DDL test: type simple, tag CREATE TYPE
+NOTICE:  DDL test: type simple, tag CREATE TYPE
+NOTICE:  DDL test: type simple, tag CREATE TYPE
+NOTICE:  DDL test: type simple, tag CREATE TYPE
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 9727408dc69..84b35ee6fe6 100644
--- a/src/test/modules/test_ddl_deparse/sql/create_schema.sql
+++ b/src/test/modules/test_ddl_deparse/sql/create_schema.sql
@@ -16,4 +16,8 @@ CREATE SCHEMA element_test
   CREATE TABLE foo (id int)
   CREATE VIEW bar AS SELECT * FROM foo
   CREATE DOMAIN d1 AS INT
-  CREATE COLLATION coll (LOCALE="C");
\ No newline at end of file
+  CREATE COLLATION coll (LOCALE="C")
+  CREATE TYPE floatrange AS RANGE (subtype = float8, subtype_diff = float8mi)
+  CREATE TYPE ss AS (a int)
+  CREATE TYPE sss
+  CREATE TYPE rainbow AS ENUM ('red', 'orange');
\ No newline at end of file
diff --git a/src/test/regress/expected/create_schema.out b/src/test/regress/expected/create_schema.out
index 3e061c7a0ef..a33cbd0c0d9 100644
--- a/src/test/regress/expected/create_schema.out
+++ b/src/test/regress/expected/create_schema.out
@@ -212,6 +212,76 @@ CREATE SCHEMA regress_schema_5 AUTHORIZATION CURRENT_ROLE
  regress_schema_5 | coll | libc     | C       | C     |        |           | yes
 (1 row)
 
+-----CREATE SCHEMA CREATE TYPE
+--fail. can not CREATE TYPE to other schema
+CREATE SCHEMA regress_schema_6 CREATE TYPE public.ss AS (a int);
+ERROR:  CREATE specifies a schema (public) different from the one being created (regress_schema_6)
+LINE 1: CREATE SCHEMA regress_schema_6 CREATE TYPE public.ss AS (a i...
+                                                   ^
+CREATE SCHEMA regress_schema_6 CREATE TYPE public.ss;
+ERROR:  CREATE TYPE specifies a schema (public) different from the one being created (regress_schema_6)
+CREATE SCHEMA regress_schema_6 CREATE TYPE public.rainbow AS ENUM ('red', 'orange');
+ERROR:  CREATE TYPE specifies a schema (public) different from the one being created (regress_schema_6)
+CREATE SCHEMA regress_schema_6 CREATE TYPE public.floatrange AS RANGE (subtype = float8, subtype_diff = float8mi);
+ERROR:  CREATE TYPE specifies a schema (regress_schema_6) different from the one being created (public)
+--fail. improper qualified name
+CREATE SCHEMA regress_schema_6 AUTHORIZATION CURRENT_ROLE
+  CREATE TYPE postgres.public.floatrange AS RANGE (subtype = float8, subtype_diff = float8mi);
+ERROR:  cross-database references are not implemented: postgres.public.floatrange
+--the following tests should error, because we execute subcommands in order
+CREATE SCHEMA regress_schema_6 CREATE TABLE tts(a ss) CREATE TYPE ss;
+ERROR:  type "ss" does not exist
+LINE 1: CREATE SCHEMA regress_schema_6 CREATE TABLE tts(a ss) CREATE...
+                                                          ^
+CREATE SCHEMA regress_schema_6 CREATE TABLE tts(a ss) CREATE TYPE ss AS (a int);
+ERROR:  type "ss" does not exist
+LINE 1: CREATE SCHEMA regress_schema_6 CREATE TABLE tts(a ss) CREATE...
+                                                          ^
+CREATE SCHEMA regress_schema_6 CREATE TABLE tts(a rainbow) CREATE TYPE rainbow AS ENUM ('red', 'orange');
+ERROR:  type "rainbow" does not exist
+LINE 1: CREATE SCHEMA regress_schema_6 CREATE TABLE tts(a rainbow) C...
+                                                          ^
+CREATE SCHEMA regress_schema_6
+  CREATE TABLE tts(a floatrange)
+  CREATE TYPE floatrange AS RANGE (subtype = float8, subtype_diff = float8mi);
+ERROR:  type "floatrange" does not exist
+LINE 2:   CREATE TABLE tts(a floatrange)
+                             ^
+--ok
+CREATE SCHEMA regress_schema_6
+  CREATE TYPE floatrange AS RANGE (subtype = float8, subtype_diff = float8mi)
+  CREATE TYPE ss AS (a int)
+  CREATE TYPE sss
+  CREATE TYPE rainbow AS ENUM ('red', 'orange');
+\dT regress_schema_6.*
+                        List of data types
+      Schema      |               Name               | Description 
+------------------+----------------------------------+-------------
+ regress_schema_6 | regress_schema_6.floatmultirange | 
+ regress_schema_6 | regress_schema_6.floatrange      | 
+ regress_schema_6 | regress_schema_6.rainbow         | 
+ regress_schema_6 | regress_schema_6.ss              | 
+ regress_schema_6 | regress_schema_6.sss             | 
+(5 rows)
+
+--schema qualified, ok
+CREATE SCHEMA regress_schema_7
+  CREATE TYPE regress_schema_7.floatrange AS RANGE (subtype = float8, subtype_diff = float8mi)
+  CREATE TYPE regress_schema_7.ss AS (a int)
+  CREATE TYPE regress_schema_7.sss
+  CREATE TYPE regress_schema_7.rainbow AS ENUM ('red', 'orange')
+  CREATE TABLE t(a floatrange, b ss, c rainbow);
+\dT regress_schema_7.*
+                        List of data types
+      Schema      |               Name               | Description 
+------------------+----------------------------------+-------------
+ regress_schema_7 | regress_schema_7.floatmultirange | 
+ regress_schema_7 | regress_schema_7.floatrange      | 
+ regress_schema_7 | regress_schema_7.rainbow         | 
+ regress_schema_7 | regress_schema_7.ss              | 
+ regress_schema_7 | regress_schema_7.sss             | 
+(5 rows)
+
 DROP SCHEMA regress_schema_2 CASCADE;
 NOTICE:  drop cascades to 2 other objects
 DETAIL:  drop cascades to type regress_schema_2.ss
@@ -230,5 +300,18 @@ DROP SCHEMA regress_schema_5 CASCADE;
 NOTICE:  drop cascades to 2 other objects
 DETAIL:  drop cascades to collation regress_schema_5.coll
 drop cascades to table regress_schema_5.t
+DROP SCHEMA regress_schema_6 CASCADE;
+NOTICE:  drop cascades to 4 other objects
+DETAIL:  drop cascades to type regress_schema_6.floatrange
+drop cascades to type regress_schema_6.ss
+drop cascades to type regress_schema_6.sss
+drop cascades to type regress_schema_6.rainbow
+DROP SCHEMA regress_schema_7 CASCADE;
+NOTICE:  drop cascades to 5 other objects
+DETAIL:  drop cascades to type regress_schema_7.floatrange
+drop cascades to type regress_schema_7.ss
+drop cascades to type regress_schema_7.sss
+drop cascades to type regress_schema_7.rainbow
+drop cascades to table regress_schema_7.t
 -- Clean up
 DROP ROLE regress_create_schema_role;
diff --git a/src/test/regress/expected/create_type.out b/src/test/regress/expected/create_type.out
index 5181c4290b4..83f63e5c8cc 100644
--- a/src/test/regress/expected/create_type.out
+++ b/src/test/regress/expected/create_type.out
@@ -35,6 +35,18 @@ CREATE FUNCTION int44out(city_budget)
 NOTICE:  argument type city_budget is only a shell
 LINE 1: CREATE FUNCTION int44out(city_budget)
                                  ^
+----- CREATE SCHEMA CREATE TYPE
+--error, cannot CREATE TYPE to other schema
+CREATE SCHEMA regress_schema_x
+   CREATE TYPE public.widget (
+      internallength = 24,
+      input = widget_in,
+      output = widget_out,
+      typmod_in = numerictypmodin,
+      typmod_out = numerictypmodout,
+      alignment = double
+   );
+ERROR:  CREATE TYPE specifies a schema (public) different from the one being created (regress_schema_x)
 CREATE TYPE widget (
    internallength = 24,
    input = widget_in,
diff --git a/src/test/regress/sql/create_schema.sql b/src/test/regress/sql/create_schema.sql
index 0f802bcaffe..75fd928eacb 100644
--- a/src/test/regress/sql/create_schema.sql
+++ b/src/test/regress/sql/create_schema.sql
@@ -131,10 +131,49 @@ CREATE SCHEMA regress_schema_5 AUTHORIZATION CURRENT_ROLE
 \dO regress_schema_5.*
 
 
+
+-----CREATE SCHEMA CREATE TYPE
+--fail. can not CREATE TYPE to other schema
+CREATE SCHEMA regress_schema_6 CREATE TYPE public.ss AS (a int);
+CREATE SCHEMA regress_schema_6 CREATE TYPE public.ss;
+CREATE SCHEMA regress_schema_6 CREATE TYPE public.rainbow AS ENUM ('red', 'orange');
+CREATE SCHEMA regress_schema_6 CREATE TYPE public.floatrange AS RANGE (subtype = float8, subtype_diff = float8mi);
+
+--fail. improper qualified name
+CREATE SCHEMA regress_schema_6 AUTHORIZATION CURRENT_ROLE
+  CREATE TYPE postgres.public.floatrange AS RANGE (subtype = float8, subtype_diff = float8mi);
+
+--the following tests should error, because we execute subcommands in order
+CREATE SCHEMA regress_schema_6 CREATE TABLE tts(a ss) CREATE TYPE ss;
+CREATE SCHEMA regress_schema_6 CREATE TABLE tts(a ss) CREATE TYPE ss AS (a int);
+CREATE SCHEMA regress_schema_6 CREATE TABLE tts(a rainbow) CREATE TYPE rainbow AS ENUM ('red', 'orange');
+CREATE SCHEMA regress_schema_6
+  CREATE TABLE tts(a floatrange)
+  CREATE TYPE floatrange AS RANGE (subtype = float8, subtype_diff = float8mi);
+
+--ok
+CREATE SCHEMA regress_schema_6
+  CREATE TYPE floatrange AS RANGE (subtype = float8, subtype_diff = float8mi)
+  CREATE TYPE ss AS (a int)
+  CREATE TYPE sss
+  CREATE TYPE rainbow AS ENUM ('red', 'orange');
+\dT regress_schema_6.*
+
+--schema qualified, ok
+CREATE SCHEMA regress_schema_7
+  CREATE TYPE regress_schema_7.floatrange AS RANGE (subtype = float8, subtype_diff = float8mi)
+  CREATE TYPE regress_schema_7.ss AS (a int)
+  CREATE TYPE regress_schema_7.sss
+  CREATE TYPE regress_schema_7.rainbow AS ENUM ('red', 'orange')
+  CREATE TABLE t(a floatrange, b ss, c rainbow);
+\dT regress_schema_7.*
+
 DROP SCHEMA regress_schema_2 CASCADE;
 DROP SCHEMA regress_schema_3 CASCADE;
 DROP SCHEMA regress_schema_4 CASCADE;
 DROP SCHEMA regress_schema_5 CASCADE;
+DROP SCHEMA regress_schema_6 CASCADE;
+DROP SCHEMA regress_schema_7 CASCADE;
 
 -- Clean up
 DROP ROLE regress_create_schema_role;
diff --git a/src/test/regress/sql/create_type.sql b/src/test/regress/sql/create_type.sql
index c25018029c2..69efd069f58 100644
--- a/src/test/regress/sql/create_type.sql
+++ b/src/test/regress/sql/create_type.sql
@@ -32,6 +32,18 @@ CREATE FUNCTION int44out(city_budget)
    AS :'regresslib'
    LANGUAGE C STRICT IMMUTABLE;
 
+----- CREATE SCHEMA CREATE TYPE
+--error, cannot CREATE TYPE to other schema
+CREATE SCHEMA regress_schema_x
+   CREATE TYPE public.widget (
+      internallength = 24,
+      input = widget_in,
+      output = widget_out,
+      typmod_in = numerictypmodin,
+      typmod_out = numerictypmodout,
+      alignment = double
+   );
+
 CREATE TYPE widget (
    internallength = 24,
    input = widget_in,
-- 
2.34.1



  [text/x-patch] v9-0002-CREATE-SCHEMA-CREATE-DOMAIN.patch (12.3K, 4-v9-0002-CREATE-SCHEMA-CREATE-DOMAIN.patch)
  download | inline diff:
From fc8c3f52c87d0fe057487a185d3735efd42eb456 Mon Sep 17 00:00:00 2001
From: jian he <[email protected]>
Date: Tue, 30 Dec 2025 11:48:49 +0800
Subject: [PATCH v9 2/5] CREATE SCHEMA CREATE DOMAIN

The SQL standard allows domains to be specified in a CREATE SCHEMA statement.
This adds support for that capability.

For example:
    CREATE SCHEMA schema_name AUTHORIZATION CURRENT_ROLE
    create domain ss as text not null;

The domain will be created within the to be created schema.  The domain name can
be schema-qualified or database-qualified, however it's not allowed to let
domain create within a different schema.

Author: Kirill Reshke <[email protected]>
Author: Jian He <[email protected]>
Reviewed-by: Alvaro Herrera <[email protected]>
Reviewed-by: Tom Lane <[email protected]>

Discussion: https://postgr.es/m/CALdSSPh4jUSDsWu3K58hjO60wnTRR0DuO4CKRcwa8EVuOSfXxg@mail.gmail.com
---
 doc/src/sgml/ref/create_schema.sgml           |  4 +-
 src/backend/parser/gram.y                     |  1 +
 src/backend/parser/parse_utilcmd.c            | 26 +++++++++
 src/bin/psql/tab-complete.in.c                | 12 ++--
 .../expected/create_schema.out                |  4 +-
 .../test_ddl_deparse/sql/create_schema.sql    |  3 +-
 src/test/regress/expected/create_schema.out   | 56 +++++++++++++++++++
 src/test/regress/sql/create_schema.sql        | 36 ++++++++++++
 8 files changed, 132 insertions(+), 10 deletions(-)

diff --git a/doc/src/sgml/ref/create_schema.sgml b/doc/src/sgml/ref/create_schema.sgml
index 625793a6b67..79186d2b936 100644
--- a/doc/src/sgml/ref/create_schema.sgml
+++ b/doc/src/sgml/ref/create_schema.sgml
@@ -100,8 +100,8 @@ CREATE SCHEMA IF NOT EXISTS AUTHORIZATION <replaceable class="parameter">role_sp
       <listitem>
        <para>
         An SQL statement defining an object to be created within the
-        schema. Currently, only <command>CREATE
-        TABLE</command>, <command>CREATE VIEW</command>, <command>CREATE
+        schema. Currently, only <command>CREATE DOMAIN</command>
+        <command>CREATE TABLE</command>, <command>CREATE VIEW</command>, <command>CREATE
         INDEX</command>, <command>CREATE SEQUENCE</command>, <command>CREATE
         TRIGGER</command> and <command>GRANT</command> are accepted as clauses
         within <command>CREATE SCHEMA</command>. Other kinds of objects may
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 28f4e11e30f..54e09d0ceb5 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -1637,6 +1637,7 @@ schema_stmt:
 			| CreateTrigStmt
 			| GrantStmt
 			| ViewStmt
+			| CreateDomainStmt
 		;
 
 
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 5d168fd0285..849a19c4e47 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -4458,6 +4458,32 @@ transformCreateSchemaStmtElements(ParseState *pstate, List *schemaElts,
 				elements = lappend(elements, element);
 				break;
 
+			case T_CreateDomainStmt:
+				{
+					char	   *domain_schema = NULL;
+
+					CreateDomainStmt *elp = castNode(CreateDomainStmt, element);
+
+					/*
+					 * The schema of the DOMAIN must match the schema being
+					 * created. If the domain name length exceeds 3, it will
+					 * fail in DeconstructQualifiedName.
+					 */
+					if (list_length(elp->domainname) == 2)
+						domain_schema = strVal(list_nth(elp->domainname, 0));
+					else if (list_length(elp->domainname) == 3)
+						domain_schema = strVal(list_nth(elp->domainname, 1));
+
+					if (domain_schema != NULL && strcmp(domain_schema, schemaName) != 0)
+						ereport(ERROR,
+								errcode(ERRCODE_INVALID_SCHEMA_DEFINITION),
+								errmsg("CREATE DOMAIN specifies a schema (%s) different from the one being created (%s)",
+									   domain_schema, schemaName));
+
+					elements = lappend(elements, element);
+				}
+				break;
+
 			default:
 				elog(ERROR, "unrecognized node type: %d",
 					 (int) nodeTag(element));
diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c
index 75a101c6ab5..e750b2dbb87 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -2201,7 +2201,7 @@ match_previous_words(int pattern_id,
 	{
 		/* only some object types can be created as part of CREATE SCHEMA */
 		if (HeadMatches("CREATE", "SCHEMA"))
-			COMPLETE_WITH("TABLE", "VIEW", "INDEX", "SEQUENCE", "TRIGGER",
+			COMPLETE_WITH("TABLE", "VIEW", "INDEX", "SEQUENCE", "TRIGGER", "DOMAIN",
 			/* for INDEX and TABLE/SEQUENCE, respectively */
 						  "UNIQUE", "UNLOGGED");
 		else
@@ -3471,15 +3471,15 @@ match_previous_words(int pattern_id,
 	else if (Matches("CREATE", "DATABASE", MatchAny, "STRATEGY"))
 		COMPLETE_WITH("WAL_LOG", "FILE_COPY");
 
-	/* CREATE DOMAIN */
-	else if (Matches("CREATE", "DOMAIN", MatchAny))
+	/* CREATE DOMAIN --- is allowed inside CREATE SCHEMA, so use TailMatches */
+	else if (TailMatches("CREATE", "DOMAIN", MatchAny))
 		COMPLETE_WITH("AS");
-	else if (Matches("CREATE", "DOMAIN", MatchAny, "AS"))
+	else if (TailMatches("CREATE", "DOMAIN", MatchAny, "AS"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes);
-	else if (Matches("CREATE", "DOMAIN", MatchAny, "AS", MatchAny))
+	else if (TailMatches("CREATE", "DOMAIN", MatchAny, "AS", MatchAny))
 		COMPLETE_WITH("COLLATE", "DEFAULT", "CONSTRAINT",
 					  "NOT NULL", "NULL", "CHECK (");
-	else if (Matches("CREATE", "DOMAIN", MatchAny, "COLLATE"))
+	else if (TailMatches("CREATE", "DOMAIN", MatchAny, "COLLATE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_collations);
 
 	/* CREATE EXTENSION */
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..d73c4702051 100644
--- a/src/test/modules/test_ddl_deparse/expected/create_schema.out
+++ b/src/test/modules/test_ddl_deparse/expected/create_schema.out
@@ -13,7 +13,9 @@ CREATE SCHEMA IF NOT EXISTS baz;
 NOTICE:  schema "baz" already exists, skipping
 CREATE SCHEMA element_test
   CREATE TABLE foo (id int)
-  CREATE VIEW bar AS SELECT * FROM foo;
+  CREATE VIEW bar AS SELECT * FROM foo
+  CREATE DOMAIN d1 AS INT;
 NOTICE:  DDL test: type simple, tag CREATE SCHEMA
 NOTICE:  DDL test: type simple, tag CREATE TABLE
 NOTICE:  DDL test: type simple, tag CREATE VIEW
+NOTICE:  DDL test: type simple, tag CREATE DOMAIN
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..57ada462070 100644
--- a/src/test/modules/test_ddl_deparse/sql/create_schema.sql
+++ b/src/test/modules/test_ddl_deparse/sql/create_schema.sql
@@ -14,4 +14,5 @@ CREATE SCHEMA IF NOT EXISTS baz;
 
 CREATE SCHEMA element_test
   CREATE TABLE foo (id int)
-  CREATE VIEW bar AS SELECT * FROM foo;
+  CREATE VIEW bar AS SELECT * FROM foo
+  CREATE DOMAIN d1 AS INT;
diff --git a/src/test/regress/expected/create_schema.out b/src/test/regress/expected/create_schema.out
index 38530c282a9..0533c29a311 100644
--- a/src/test/regress/expected/create_schema.out
+++ b/src/test/regress/expected/create_schema.out
@@ -131,5 +131,61 @@ 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;
+-- Cases where the schema creation with domain.
+--fail. cannot CREATE DOMAIN to other schema
+CREATE SCHEMA regress_schema_2 AUTHORIZATION CURRENT_ROLE
+  CREATE DOMAIN public.ss AS TEXT NOT NULL DEFAULT 'hello' CONSTRAINT nn CHECK (VALUE <> 'hello')
+  CREATE TABLE t(a ss);
+ERROR:  CREATE DOMAIN specifies a schema (public) different from the one being created (regress_schema_2)
+--fail. cannot CREATE DOMAIN to other schema
+CREATE SCHEMA regress_schema_2 AUTHORIZATION CURRENT_ROLE
+  CREATE DOMAIN postgres.public.ss AS TEXT NOT NULL CONSTRAINT nn CHECK (VALUE <> 'hello') DEFAULT 'hello'
+  CREATE TABLE t(a ss);
+ERROR:  CREATE DOMAIN specifies a schema (public) different from the one being created (regress_schema_2)
+--fail. improper qualified name
+CREATE SCHEMA regress_schema_2 CREATE DOMAIN ss.postgres.regress_schema_2.ss AS TEXT;
+ERROR:  improper qualified name (too many dotted names): ss.postgres.regress_schema_2.ss
+--fail. Execute subcommands in order; we do not implicitly reorder them.
+CREATE SCHEMA regress_schema_2 AUTHORIZATION CURRENT_ROLE
+  CREATE DOMAIN ss1 AS ss
+  CREATE DOMAIN ss AS TEXT;
+ERROR:  type "ss" does not exist
+LINE 2:   CREATE DOMAIN ss1 AS ss
+                               ^
+--ok, qualified schema name for domain should be same as the created schema.
+CREATE SCHEMA regress_schema_2 AUTHORIZATION CURRENT_ROLE
+  CREATE DOMAIN regress_schema_2.ss AS TEXT NOT NULL CONSTRAINT nn CHECK (VALUE <> 'hello') DEFAULT 'hello' COLLATE "C"
+  CREATE TABLE t(a regress_schema_2.ss);
+\dD regress_schema_2.*
+                                            List of domains
+      Schema      | Name | Type | Collation | Nullable |    Default    |             Check              
+------------------+------+------+-----------+----------+---------------+--------------------------------
+ regress_schema_2 | ss   | text | C         | not null | 'hello'::text | CHECK (VALUE <> 'hello'::text)
+(1 row)
+
+--ok, no qualified schema name for domain.
+CREATE SCHEMA regress_schema_3 AUTHORIZATION CURRENT_ROLE
+  CREATE DOMAIN ss AS TEXT CONSTRAINT nn CHECK (VALUE <> 'hello') NOT NULL DEFAULT 'hello'
+  CREATE DOMAIN ss1 AS ss
+  CREATE VIEW test AS SELECT 'hello'::ss AS test
+  CREATE TABLE t(a ss1);
+\dD regress_schema_3.*
+                                                    List of domains
+      Schema      | Name |        Type         | Collation | Nullable |    Default    |             Check              
+------------------+------+---------------------+-----------+----------+---------------+--------------------------------
+ regress_schema_3 | ss   | text                |           | not null | 'hello'::text | CHECK (VALUE <> 'hello'::text)
+ regress_schema_3 | ss1  | regress_schema_3.ss |           |          | 'hello'::text | 
+(2 rows)
+
+DROP SCHEMA regress_schema_2 CASCADE;
+NOTICE:  drop cascades to 2 other objects
+DETAIL:  drop cascades to type regress_schema_2.ss
+drop cascades to table regress_schema_2.t
+DROP SCHEMA regress_schema_3 CASCADE;
+NOTICE:  drop cascades to 4 other objects
+DETAIL:  drop cascades to type regress_schema_3.ss
+drop cascades to type regress_schema_3.ss1
+drop cascades to view regress_schema_3.test
+drop cascades to table regress_schema_3.t
 -- Clean up
 DROP ROLE regress_create_schema_role;
diff --git a/src/test/regress/sql/create_schema.sql b/src/test/regress/sql/create_schema.sql
index b3dc1cfd758..54a07054767 100644
--- a/src/test/regress/sql/create_schema.sql
+++ b/src/test/regress/sql/create_schema.sql
@@ -71,5 +71,41 @@ CREATE SCHEMA regress_schema_1 AUTHORIZATION CURRENT_ROLE
 DROP SCHEMA regress_schema_1 CASCADE;
 RESET ROLE;
 
+-- Cases where the schema creation with domain.
+--fail. cannot CREATE DOMAIN to other schema
+CREATE SCHEMA regress_schema_2 AUTHORIZATION CURRENT_ROLE
+  CREATE DOMAIN public.ss AS TEXT NOT NULL DEFAULT 'hello' CONSTRAINT nn CHECK (VALUE <> 'hello')
+  CREATE TABLE t(a ss);
+
+--fail. cannot CREATE DOMAIN to other schema
+CREATE SCHEMA regress_schema_2 AUTHORIZATION CURRENT_ROLE
+  CREATE DOMAIN postgres.public.ss AS TEXT NOT NULL CONSTRAINT nn CHECK (VALUE <> 'hello') DEFAULT 'hello'
+  CREATE TABLE t(a ss);
+
+--fail. improper qualified name
+CREATE SCHEMA regress_schema_2 CREATE DOMAIN ss.postgres.regress_schema_2.ss AS TEXT;
+
+--fail. Execute subcommands in order; we do not implicitly reorder them.
+CREATE SCHEMA regress_schema_2 AUTHORIZATION CURRENT_ROLE
+  CREATE DOMAIN ss1 AS ss
+  CREATE DOMAIN ss AS TEXT;
+
+--ok, qualified schema name for domain should be same as the created schema.
+CREATE SCHEMA regress_schema_2 AUTHORIZATION CURRENT_ROLE
+  CREATE DOMAIN regress_schema_2.ss AS TEXT NOT NULL CONSTRAINT nn CHECK (VALUE <> 'hello') DEFAULT 'hello' COLLATE "C"
+  CREATE TABLE t(a regress_schema_2.ss);
+\dD regress_schema_2.*
+
+--ok, no qualified schema name for domain.
+CREATE SCHEMA regress_schema_3 AUTHORIZATION CURRENT_ROLE
+  CREATE DOMAIN ss AS TEXT CONSTRAINT nn CHECK (VALUE <> 'hello') NOT NULL DEFAULT 'hello'
+  CREATE DOMAIN ss1 AS ss
+  CREATE VIEW test AS SELECT 'hello'::ss AS test
+  CREATE TABLE t(a ss1);
+\dD regress_schema_3.*
+
+DROP SCHEMA regress_schema_2 CASCADE;
+DROP SCHEMA regress_schema_3 CASCADE;
+
 -- Clean up
 DROP ROLE regress_create_schema_role;
-- 
2.34.1



  [text/x-patch] v9-0001-Don-t-try-to-re-order-the-subcommands-of-CREATE-SCHEMA.patch (26.3K, 5-v9-0001-Don-t-try-to-re-order-the-subcommands-of-CREATE-SCHEMA.patch)
  download | inline diff:
From d12c2e257a976289d6b6e3e7174354d20d1caec1 Mon Sep 17 00:00:00 2001
From: jian he <[email protected]>
Date: Wed, 17 Dec 2025 15:31:17 +0800
Subject: [PATCH v9 1/5] Don't try to re-order the subcommands of CREATE SCHEMA
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

transformCreateSchemaStmtElements has always believed that it is
supposed to re-order the subcommands of CREATE SCHEMA into a safe
execution order.  However, it is nowhere near being capable of doing
that correctly.  Nor is there reason to think that it ever will be,
or that that is a well-defined requirement, or that there's any basis
in the SQL standard for it.  Moreover, the problem will get worse as
we add more subcommand types.  Let's just drop the whole idea and
execute the commands in the order given, which seems like a much less
astonishment-prone definition anyway.

Along the way, pass down a ParseState so that we can provide an
error cursor for the "wrong schema name" error, and fix
transformCreateSchemaStmtElements so that it doesn't scribble
on the parsetree passed to it.

Note: This will cause compatibility issue,
for example:
CREATE SCHEMA regress_schema_2
  CREATE VIEW abcd_view AS SELECT a FROM abcd
  CREATE TABLE abcd (a int);

With the patch, it will throw an error, whereas on HEAD it won’t.

Discussion: https://postgr.es/m/[email protected]
---
 doc/src/sgml/ref/create_schema.sgml         |  10 +-
 src/backend/commands/extension.c            |   7 +-
 src/backend/commands/schemacmds.c           |  15 ++-
 src/backend/parser/parse_utilcmd.c          | 136 ++++++++------------
 src/backend/tcop/utility.c                  |   3 +-
 src/include/commands/schemacmds.h           |   7 +-
 src/include/parser/parse_utilcmd.h          |   3 +-
 src/test/regress/expected/create_schema.out |  37 ++++++
 src/test/regress/expected/create_view.out   |   2 +
 src/test/regress/expected/event_trigger.out |   2 +-
 src/test/regress/expected/namespace.out     |   7 +-
 src/test/regress/sql/create_schema.sql      |   5 +
 src/test/regress/sql/namespace.sql          |   8 +-
 src/tools/pgindent/typedefs.list            |   1 -
 14 files changed, 125 insertions(+), 118 deletions(-)

diff --git a/doc/src/sgml/ref/create_schema.sgml b/doc/src/sgml/ref/create_schema.sgml
index ed69298ccc6..625793a6b67 100644
--- a/doc/src/sgml/ref/create_schema.sgml
+++ b/doc/src/sgml/ref/create_schema.sgml
@@ -193,12 +193,10 @@ CREATE VIEW hollywood.winners AS
   </para>
 
   <para>
-   The SQL standard specifies that the subcommands in <command>CREATE
-   SCHEMA</command> can appear in any order.  The present
-   <productname>PostgreSQL</productname> implementation does not
-   handle all cases of forward references in subcommands; it might
-   sometimes be necessary to reorder the subcommands in order to avoid
-   forward references.
+   <productname>PostgreSQL</productname> executes the subcommands
+   in <command>CREATE SCHEMA</command> in the order given.  Other
+   implementations may try to rearrange the subcommands into dependency
+   order, but that is hard if not impossible to do correctly.
   </para>
 
   <para>
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index c43b74e319e..58e2421b008 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1773,14 +1773,17 @@ CreateExtensionInternal(char *extensionName,
 
 		if (!OidIsValid(schemaOid))
 		{
+			ParseState *pstate = make_parsestate(NULL);
 			CreateSchemaStmt *csstmt = makeNode(CreateSchemaStmt);
 
+			pstate->p_sourcetext = "(generated CREATE SCHEMA command)";
+
 			csstmt->schemaname = schemaName;
 			csstmt->authrole = NULL;	/* will be created by current user */
 			csstmt->schemaElts = NIL;
 			csstmt->if_not_exists = false;
-			CreateSchemaCommand(csstmt, "(generated CREATE SCHEMA command)",
-								-1, -1);
+
+			CreateSchemaCommand(pstate, csstmt, -1, -1);
 
 			/*
 			 * CreateSchemaCommand includes CommandCounterIncrement, so new
diff --git a/src/backend/commands/schemacmds.c b/src/backend/commands/schemacmds.c
index 3cc1472103a..09928c58d9d 100644
--- a/src/backend/commands/schemacmds.c
+++ b/src/backend/commands/schemacmds.c
@@ -49,7 +49,7 @@ static void AlterSchemaOwner_internal(HeapTuple tup, Relation rel, Oid newOwnerI
  * a subquery.
  */
 Oid
-CreateSchemaCommand(CreateSchemaStmt *stmt, const char *queryString,
+CreateSchemaCommand(ParseState *pstate, CreateSchemaStmt *stmt,
 					int stmt_location, int stmt_len)
 {
 	const char *schemaName = stmt->schemaname;
@@ -189,12 +189,13 @@ CreateSchemaCommand(CreateSchemaStmt *stmt, const char *queryString,
 
 	/*
 	 * Examine the list of commands embedded in the CREATE SCHEMA command, and
-	 * reorganize them into a sequentially executable order with no forward
-	 * references.  Note that the result is still a list of raw parsetrees ---
-	 * we cannot, in general, run parse analysis on one statement until we
-	 * have actually executed the prior ones.
+	 * do preliminary transformations (mostly, verify that none are trying to
+	 * create objects outside the new schema).  Note that the result is still
+	 * a list of raw parsetrees --- we cannot, in general, run parse analysis
+	 * on one statement until we have actually executed the prior ones.
 	 */
-	parsetree_list = transformCreateSchemaStmtElements(stmt->schemaElts,
+	parsetree_list = transformCreateSchemaStmtElements(pstate,
+													   stmt->schemaElts,
 													   schemaName);
 
 	/*
@@ -219,7 +220,7 @@ CreateSchemaCommand(CreateSchemaStmt *stmt, const char *queryString,
 
 		/* do this step */
 		ProcessUtility(wrapper,
-					   queryString,
+					   pstate->p_sourcetext,
 					   false,
 					   PROCESS_UTILITY_SUBCOMMAND,
 					   NULL,
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 2b7b084f216..5d168fd0285 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -98,18 +98,6 @@ typedef struct
 	bool		ofType;			/* true if statement contains OF typename */
 } CreateStmtContext;
 
-/* State shared by transformCreateSchemaStmtElements and its subroutines */
-typedef struct
-{
-	const char *schemaname;		/* name of schema */
-	List	   *sequences;		/* CREATE SEQUENCE items */
-	List	   *tables;			/* CREATE TABLE items */
-	List	   *views;			/* CREATE VIEW items */
-	List	   *indexes;		/* CREATE INDEX items */
-	List	   *triggers;		/* CREATE TRIGGER items */
-	List	   *grants;			/* GRANT items */
-} CreateSchemaStmtContext;
-
 
 static void transformColumnDefinition(CreateStmtContext *cxt,
 									  ColumnDef *column);
@@ -136,7 +124,8 @@ static void transformCheckConstraints(CreateStmtContext *cxt,
 static void transformConstraintAttrs(CreateStmtContext *cxt,
 									 List *constraintList);
 static void transformColumnType(CreateStmtContext *cxt, ColumnDef *column);
-static void setSchemaName(const char *context_schema, char **stmt_schema_name);
+static void checkSchemaName(ParseState *pstate, const char *context_schema,
+							RangeVar *relation);
 static void transformPartitionCmd(CreateStmtContext *cxt, PartitionBoundSpec *bound);
 static List *transformPartitionRangeBounds(ParseState *pstate, List *blist,
 										   Relation parent);
@@ -4388,51 +4377,35 @@ transformColumnType(CreateStmtContext *cxt, ColumnDef *column)
  * transformCreateSchemaStmtElements -
  *	  analyzes the elements of a CREATE SCHEMA statement
  *
- * Split the schema element list from a CREATE SCHEMA statement into
- * individual commands and place them in the result list in an order
- * such that there are no forward references (e.g. GRANT to a table
- * created later in the list). Note that the logic we use for determining
- * forward references is presently quite incomplete.
+ * 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.
+ * 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.
  *
  * "schemaName" is the name of the schema that will be used for the creation
- * of the objects listed, that may be compiled from the schema name defined
+ * of the objects listed.  It may be obtained from the schema name defined
  * in the statement or a role specification.
  *
- * SQL also allows constraints to make forward references, so thumb through
- * the table columns and move forward references to a posterior alter-table
- * command.
- *
  * 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: this breaks the rules a little bit by modifying schema-name fields
- * within passed-in structs.  However, the transformation would be the same
- * if done over, so it should be all right to scribble on the input to this
- * extent.
  */
 List *
-transformCreateSchemaStmtElements(List *schemaElts, const char *schemaName)
+transformCreateSchemaStmtElements(ParseState *pstate, List *schemaElts,
+								  const char *schemaName)
 {
-	CreateSchemaStmtContext cxt;
-	List	   *result;
-	ListCell   *elements;
-
-	cxt.schemaname = schemaName;
-	cxt.sequences = NIL;
-	cxt.tables = NIL;
-	cxt.views = NIL;
-	cxt.indexes = NIL;
-	cxt.triggers = NIL;
-	cxt.grants = NIL;
+	List	   *elements = NIL;
+	ListCell   *lc;
 
 	/*
-	 * Run through each schema element in the schema element list. Separate
-	 * statements by type, and do preliminary analysis.
+	 * Run through each schema element in the schema element list.  Check
+	 * target schema names, and collect the list of actions to be done.
 	 */
-	foreach(elements, schemaElts)
+	foreach(lc, schemaElts)
 	{
-		Node	   *element = lfirst(elements);
+		Node	   *element = lfirst(lc);
 
 		switch (nodeTag(element))
 		{
@@ -4440,8 +4413,8 @@ transformCreateSchemaStmtElements(List *schemaElts, const char *schemaName)
 				{
 					CreateSeqStmt *elp = (CreateSeqStmt *) element;
 
-					setSchemaName(cxt.schemaname, &elp->sequence->schemaname);
-					cxt.sequences = lappend(cxt.sequences, element);
+					checkSchemaName(pstate, schemaName, elp->sequence);
+					elements = lappend(elements, element);
 				}
 				break;
 
@@ -4449,12 +4422,8 @@ transformCreateSchemaStmtElements(List *schemaElts, const char *schemaName)
 				{
 					CreateStmt *elp = (CreateStmt *) element;
 
-					setSchemaName(cxt.schemaname, &elp->relation->schemaname);
-
-					/*
-					 * XXX todo: deal with constraints
-					 */
-					cxt.tables = lappend(cxt.tables, element);
+					checkSchemaName(pstate, schemaName, elp->relation);
+					elements = lappend(elements, element);
 				}
 				break;
 
@@ -4462,12 +4431,8 @@ transformCreateSchemaStmtElements(List *schemaElts, const char *schemaName)
 				{
 					ViewStmt   *elp = (ViewStmt *) element;
 
-					setSchemaName(cxt.schemaname, &elp->view->schemaname);
-
-					/*
-					 * XXX todo: deal with references between views
-					 */
-					cxt.views = lappend(cxt.views, element);
+					checkSchemaName(pstate, schemaName, elp->view);
+					elements = lappend(elements, element);
 				}
 				break;
 
@@ -4475,8 +4440,8 @@ transformCreateSchemaStmtElements(List *schemaElts, const char *schemaName)
 				{
 					IndexStmt  *elp = (IndexStmt *) element;
 
-					setSchemaName(cxt.schemaname, &elp->relation->schemaname);
-					cxt.indexes = lappend(cxt.indexes, element);
+					checkSchemaName(pstate, schemaName, elp->relation);
+					elements = lappend(elements, element);
 				}
 				break;
 
@@ -4484,13 +4449,13 @@ transformCreateSchemaStmtElements(List *schemaElts, const char *schemaName)
 				{
 					CreateTrigStmt *elp = (CreateTrigStmt *) element;
 
-					setSchemaName(cxt.schemaname, &elp->relation->schemaname);
-					cxt.triggers = lappend(cxt.triggers, element);
+					checkSchemaName(pstate, schemaName, elp->relation);
+					elements = lappend(elements, element);
 				}
 				break;
 
 			case T_GrantStmt:
-				cxt.grants = lappend(cxt.grants, element);
+				elements = lappend(elements, element);
 				break;
 
 			default:
@@ -4499,32 +4464,39 @@ transformCreateSchemaStmtElements(List *schemaElts, const char *schemaName)
 		}
 	}
 
-	result = NIL;
-	result = list_concat(result, cxt.sequences);
-	result = list_concat(result, cxt.tables);
-	result = list_concat(result, cxt.views);
-	result = list_concat(result, cxt.indexes);
-	result = list_concat(result, cxt.triggers);
-	result = list_concat(result, cxt.grants);
-
-	return result;
+	return elements;
 }
 
 /*
- * setSchemaName
- *		Set or check schema name in an element of a CREATE SCHEMA command
+ * checkSchemaName
+ *		Check schema name in an element of a CREATE SCHEMA command
+ *
+ * It's okay if the command doesn't specify a target schema name, because
+ * CreateSchemaCommand will set up the default creation schema to be the
+ * new schema.  But if a target schema name is given, it had better match.
+ * We also have to check that the command doesn't say CREATE TEMP, since
+ * that would likewise put the object into the wrong schema.
  */
 static void
-setSchemaName(const char *context_schema, char **stmt_schema_name)
+checkSchemaName(ParseState *pstate, const char *context_schema,
+				RangeVar *relation)
 {
-	if (*stmt_schema_name == NULL)
-		*stmt_schema_name = unconstify(char *, context_schema);
-	else if (strcmp(context_schema, *stmt_schema_name) != 0)
+	if (relation->schemaname != NULL &&
+		strcmp(context_schema, relation->schemaname) != 0)
 		ereport(ERROR,
-				(errcode(ERRCODE_INVALID_SCHEMA_DEFINITION),
-				 errmsg("CREATE specifies a schema (%s) "
-						"different from the one being created (%s)",
-						*stmt_schema_name, context_schema)));
+				errcode(ERRCODE_INVALID_SCHEMA_DEFINITION),
+				errmsg("CREATE specifies a schema (%s) different from the one being created (%s)",
+					   relation->schemaname, context_schema),
+				parser_errposition(pstate, relation->location));
+
+	if (relation->relpersistence == RELPERSISTENCE_TEMP)
+	{
+		/* spell this error the same as in RangeVarAdjustRelationPersistence */
+		ereport(ERROR,
+				errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+				errmsg("cannot create temporary relation in non-temporary schema"),
+				parser_errposition(pstate, relation->location));
+	}
 }
 
 /*
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index d18a3a60a46..5e8cd97f3c3 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1119,8 +1119,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				 * relation and attribute manipulation
 				 */
 			case T_CreateSchemaStmt:
-				CreateSchemaCommand((CreateSchemaStmt *) parsetree,
-									queryString,
+				CreateSchemaCommand(pstate, castNode(CreateSchemaStmt, parsetree),
 									pstmt->stmt_location,
 									pstmt->stmt_len);
 
diff --git a/src/include/commands/schemacmds.h b/src/include/commands/schemacmds.h
index 8557176b66a..835265bb67c 100644
--- a/src/include/commands/schemacmds.h
+++ b/src/include/commands/schemacmds.h
@@ -16,12 +16,9 @@
 #define SCHEMACMDS_H
 
 #include "catalog/objectaddress.h"
-#include "nodes/parsenodes.h"
-
-extern Oid	CreateSchemaCommand(CreateSchemaStmt *stmt,
-								const char *queryString,
-								int stmt_location, int stmt_len);
+#include "parser/parse_node.h"
 
+extern Oid	CreateSchemaCommand(ParseState *pstate, CreateSchemaStmt *stmt, int stmt_location, int stmt_len);
 extern ObjectAddress RenameSchema(const char *oldname, const char *newname);
 extern ObjectAddress AlterSchemaOwner(const char *name, Oid newOwnerId);
 extern void AlterSchemaOwner_oid(Oid schemaoid, Oid newOwnerId);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..d151bba03eb 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -30,7 +30,8 @@ extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-extern List *transformCreateSchemaStmtElements(List *schemaElts,
+extern List *transformCreateSchemaStmtElements(ParseState *pstate,
+											   List *schemaElts,
 											   const char *schemaName);
 extern PartitionBoundSpec *transformPartitionBound(ParseState *pstate, Relation parent,
 												   PartitionBoundSpec *spec);
diff --git a/src/test/regress/expected/create_schema.out b/src/test/regress/expected/create_schema.out
index 93302a07efc..38530c282a9 100644
--- a/src/test/regress/expected/create_schema.out
+++ b/src/test/regress/expected/create_schema.out
@@ -9,55 +9,92 @@ CREATE ROLE regress_create_schema_role SUPERUSER;
 CREATE SCHEMA AUTHORIZATION regress_create_schema_role
   CREATE SEQUENCE schema_not_existing.seq;
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_create_schema_role)
+LINE 2:   CREATE SEQUENCE schema_not_existing.seq;
+                          ^
 CREATE SCHEMA AUTHORIZATION regress_create_schema_role
   CREATE TABLE schema_not_existing.tab (id int);
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_create_schema_role)
+LINE 2:   CREATE TABLE schema_not_existing.tab (id int);
+                       ^
 CREATE SCHEMA AUTHORIZATION regress_create_schema_role
   CREATE VIEW schema_not_existing.view AS SELECT 1;
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_create_schema_role)
+LINE 2:   CREATE VIEW schema_not_existing.view AS SELECT 1;
+                      ^
 CREATE SCHEMA AUTHORIZATION regress_create_schema_role
   CREATE INDEX ON schema_not_existing.tab (id);
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_create_schema_role)
+LINE 2:   CREATE INDEX ON schema_not_existing.tab (id);
+                          ^
 CREATE SCHEMA AUTHORIZATION regress_create_schema_role
   CREATE TRIGGER schema_trig BEFORE INSERT ON schema_not_existing.tab
   EXECUTE FUNCTION schema_trig.no_func();
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_create_schema_role)
+LINE 2:   CREATE TRIGGER schema_trig BEFORE INSERT ON schema_not_exi...
+                                                      ^
 -- Again, with a role specification and no schema names.
 SET ROLE regress_create_schema_role;
 CREATE SCHEMA AUTHORIZATION CURRENT_ROLE
   CREATE SEQUENCE schema_not_existing.seq;
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_create_schema_role)
+LINE 2:   CREATE SEQUENCE schema_not_existing.seq;
+                          ^
 CREATE SCHEMA AUTHORIZATION CURRENT_ROLE
   CREATE TABLE schema_not_existing.tab (id int);
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_create_schema_role)
+LINE 2:   CREATE TABLE schema_not_existing.tab (id int);
+                       ^
 CREATE SCHEMA AUTHORIZATION CURRENT_ROLE
   CREATE VIEW schema_not_existing.view AS SELECT 1;
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_create_schema_role)
+LINE 2:   CREATE VIEW schema_not_existing.view AS SELECT 1;
+                      ^
 CREATE SCHEMA AUTHORIZATION CURRENT_ROLE
   CREATE INDEX ON schema_not_existing.tab (id);
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_create_schema_role)
+LINE 2:   CREATE INDEX ON schema_not_existing.tab (id);
+                          ^
 CREATE SCHEMA AUTHORIZATION CURRENT_ROLE
   CREATE TRIGGER schema_trig BEFORE INSERT ON schema_not_existing.tab
   EXECUTE FUNCTION schema_trig.no_func();
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_create_schema_role)
+LINE 2:   CREATE TRIGGER schema_trig BEFORE INSERT ON schema_not_exi...
+                                                      ^
 -- Again, with a schema name and a role specification.
 CREATE SCHEMA regress_schema_1 AUTHORIZATION CURRENT_ROLE
   CREATE SEQUENCE schema_not_existing.seq;
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_schema_1)
+LINE 2:   CREATE SEQUENCE schema_not_existing.seq;
+                          ^
 CREATE SCHEMA regress_schema_1 AUTHORIZATION CURRENT_ROLE
   CREATE TABLE schema_not_existing.tab (id int);
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_schema_1)
+LINE 2:   CREATE TABLE schema_not_existing.tab (id int);
+                       ^
 CREATE SCHEMA regress_schema_1 AUTHORIZATION CURRENT_ROLE
   CREATE VIEW schema_not_existing.view AS SELECT 1;
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_schema_1)
+LINE 2:   CREATE VIEW schema_not_existing.view AS SELECT 1;
+                      ^
 CREATE SCHEMA regress_schema_1 AUTHORIZATION CURRENT_ROLE
   CREATE INDEX ON schema_not_existing.tab (id);
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_schema_1)
+LINE 2:   CREATE INDEX ON schema_not_existing.tab (id);
+                          ^
 CREATE SCHEMA regress_schema_1 AUTHORIZATION CURRENT_ROLE
   CREATE TRIGGER schema_trig BEFORE INSERT ON schema_not_existing.tab
   EXECUTE FUNCTION schema_trig.no_func();
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_schema_1)
+LINE 2:   CREATE TRIGGER schema_trig BEFORE INSERT ON schema_not_exi...
+                                                      ^
 RESET ROLE;
+--error. Forward reference is not allowed.
+CREATE SCHEMA regress_schema_2
+  CREATE VIEW abcd_view AS SELECT a FROM abcd
+  CREATE TABLE abcd (a int);
+ERROR:  relation "abcd" does not exist
+LINE 2:   CREATE VIEW abcd_view AS SELECT a FROM abcd
+                                                 ^
 -- Cases where the schema creation succeeds.
 -- The schema created matches the role name.
 CREATE SCHEMA AUTHORIZATION regress_create_schema_role
diff --git a/src/test/regress/expected/create_view.out b/src/test/regress/expected/create_view.out
index bc4f79938b3..63cf4b4371d 100644
--- a/src/test/regress/expected/create_view.out
+++ b/src/test/regress/expected/create_view.out
@@ -128,6 +128,8 @@ ERROR:  cannot create temporary relation in non-temporary schema
 CREATE SCHEMA test_view_schema
     CREATE TEMP VIEW testview AS SELECT 1;
 ERROR:  cannot create temporary relation in non-temporary schema
+LINE 2:     CREATE TEMP VIEW testview AS SELECT 1;
+                             ^
 -- joins: if any of the join relations are temporary, the view
 -- should also be temporary
 -- should be non-temp
diff --git a/src/test/regress/expected/event_trigger.out b/src/test/regress/expected/event_trigger.out
index 16e4530708c..4a7fd2bc59a 100644
--- a/src/test/regress/expected/event_trigger.out
+++ b/src/test/regress/expected/event_trigger.out
@@ -424,12 +424,12 @@ NOTICE:  END: command_tag=CREATE TABLE type=table identity=evttrig.one
 NOTICE:  END: command_tag=CREATE INDEX type=index identity=evttrig.one_pkey
 NOTICE:  END: command_tag=ALTER SEQUENCE type=sequence identity=evttrig.one_col_a_seq
 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 INDEX type=index identity=evttrig.one_idx
 -- Partitioned tables with a partitioned index
 CREATE TABLE evttrig.parted (
     id int PRIMARY KEY)
diff --git a/src/test/regress/expected/namespace.out b/src/test/regress/expected/namespace.out
index dbbda72d395..d02f3fd67d7 100644
--- a/src/test/regress/expected/namespace.out
+++ b/src/test/regress/expected/namespace.out
@@ -10,13 +10,10 @@ SELECT pg_catalog.set_config('search_path', ' ', false);
 (1 row)
 
 CREATE SCHEMA test_ns_schema_1
+       CREATE TABLE abc (a serial, b int UNIQUE)
        CREATE UNIQUE INDEX abc_a_idx ON abc (a)
        CREATE VIEW abc_view AS
-              SELECT a+1 AS a, b+1 AS b FROM abc
-       CREATE TABLE abc (
-              a serial,
-              b int UNIQUE
-       );
+              SELECT a+1 AS a, b+1 AS b FROM abc;
 -- verify that the correct search_path restored on abort
 SET search_path to public;
 BEGIN;
diff --git a/src/test/regress/sql/create_schema.sql b/src/test/regress/sql/create_schema.sql
index 1b7064247a1..b3dc1cfd758 100644
--- a/src/test/regress/sql/create_schema.sql
+++ b/src/test/regress/sql/create_schema.sql
@@ -47,6 +47,11 @@ CREATE SCHEMA regress_schema_1 AUTHORIZATION CURRENT_ROLE
   EXECUTE FUNCTION schema_trig.no_func();
 RESET ROLE;
 
+--error. Forward reference is not allowed.
+CREATE SCHEMA regress_schema_2
+  CREATE VIEW abcd_view AS SELECT a FROM abcd
+  CREATE TABLE abcd (a int);
+
 -- Cases where the schema creation succeeds.
 -- The schema created matches the role name.
 CREATE SCHEMA AUTHORIZATION regress_create_schema_role
diff --git a/src/test/regress/sql/namespace.sql b/src/test/regress/sql/namespace.sql
index 306cdc2d8c6..9433eb3c15c 100644
--- a/src/test/regress/sql/namespace.sql
+++ b/src/test/regress/sql/namespace.sql
@@ -7,15 +7,11 @@
 SELECT pg_catalog.set_config('search_path', ' ', false);
 
 CREATE SCHEMA test_ns_schema_1
+       CREATE TABLE abc (a serial, b int UNIQUE)
        CREATE UNIQUE INDEX abc_a_idx ON abc (a)
 
        CREATE VIEW abc_view AS
-              SELECT a+1 AS a, b+1 AS b FROM abc
-
-       CREATE TABLE abc (
-              a serial,
-              b int UNIQUE
-       );
+              SELECT a+1 AS a, b+1 AS b FROM abc;
 
 -- verify that the correct search_path restored on abort
 SET search_path to public;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index ceb3fc5d980..be588bd01e4 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -569,7 +569,6 @@ CreateRangeStmt
 CreateReplicationSlotCmd
 CreateRoleStmt
 CreateSchemaStmt
-CreateSchemaStmtContext
 CreateSeqStmt
 CreateStatsStmt
 CreateStmt
-- 
2.34.1



  [text/x-patch] v9-0003-CREATE-SCHEMA-CREATE-COLLATION.patch (13.9K, 6-v9-0003-CREATE-SCHEMA-CREATE-COLLATION.patch)
  download | inline diff:
From f585d3966cedf6046a863a7a9018a3ffd8399559 Mon Sep 17 00:00:00 2001
From: jian he <[email protected]>
Date: Tue, 30 Dec 2025 13:40:48 +0800
Subject: [PATCH v9 3/5] CREATE SCHEMA CREATE COLLATION

The SQL standard allows collation to be specified in a CREATE SCHEMA statement.
This adds support for that capability.

For example:
    CREATE SCHEMA schema_name CREATE COLLATION coll_icu_und FROM "und-x-icu";

The collation will be created within the to be created schema.  The collation
name can be schema-qualified or database-qualified, however it's not allowed to
let collation create within a different schema.

Discussion: https://postgr.es/m/CALdSSPh4jUSDsWu3K58hjO60wnTRR0DuO4CKRcwa8EVuOSfXxg@mail.gmail.com
---
 doc/src/sgml/ref/create_schema.sgml           |  3 +-
 src/backend/catalog/objectaddress.c           | 18 ++++++++
 src/backend/parser/gram.y                     |  1 +
 src/backend/parser/parse_utilcmd.c            | 40 +++++++++++++++++
 src/include/catalog/objectaddress.h           |  1 +
 .../expected/create_schema.out                |  4 +-
 .../test_ddl_deparse/sql/create_schema.sql    |  3 +-
 .../regress/expected/collate.icu.utf8.out     | 15 +++++++
 src/test/regress/expected/create_schema.out   | 43 +++++++++++++++++++
 src/test/regress/sql/collate.icu.utf8.sql     |  7 +++
 src/test/regress/sql/create_schema.sql        | 29 +++++++++++++
 11 files changed, 161 insertions(+), 3 deletions(-)

diff --git a/doc/src/sgml/ref/create_schema.sgml b/doc/src/sgml/ref/create_schema.sgml
index 79186d2b936..d8273bb2d0c 100644
--- a/doc/src/sgml/ref/create_schema.sgml
+++ b/doc/src/sgml/ref/create_schema.sgml
@@ -100,7 +100,8 @@ CREATE SCHEMA IF NOT EXISTS AUTHORIZATION <replaceable class="parameter">role_sp
       <listitem>
        <para>
         An SQL statement defining an object to be created within the
-        schema. Currently, only <command>CREATE DOMAIN</command>
+        schema. Currently, only <command>CREATE COLLATION</command>,
+        <command>CREATE DOMAIN</command>
         <command>CREATE TABLE</command>, <command>CREATE VIEW</command>, <command>CREATE
         INDEX</command>, <command>CREATE SEQUENCE</command>, <command>CREATE
         TRIGGER</command> and <command>GRANT</command> are accepted as clauses
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index fa6c6df598a..f32052084dc 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -2621,6 +2621,24 @@ read_objtype_from_string(const char *objtype)
 	return -1;					/* keep compiler quiet */
 }
 
+/* get the ObjectType name */
+const char *
+stringify_objtype(ObjectType objtype)
+{
+	for (int i = 0; i < lengthof(ObjectTypeMap); i++)
+	{
+		if (ObjectTypeMap[i].tm_type == objtype)
+			return ObjectTypeMap[i].tm_name;
+	}
+
+	ereport(ERROR,
+			errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+			errmsg("unrecognized object type %d", objtype));
+
+	return NULL;				/* keep compiler quiet */
+}
+
+
 /*
  * Interfaces to reference fields of ObjectPropertyType
  */
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 54e09d0ceb5..07b7bf0ab5c 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -1638,6 +1638,7 @@ schema_stmt:
 			| GrantStmt
 			| ViewStmt
 			| CreateDomainStmt
+			| DefineStmt
 		;
 
 
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 849a19c4e47..af164e360b5 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -64,6 +64,7 @@
 #include "rewrite/rewriteManip.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
+#include "utils/formatting.h"
 #include "utils/lsyscache.h"
 #include "utils/partcache.h"
 #include "utils/rel.h"
@@ -4484,6 +4485,45 @@ transformCreateSchemaStmtElements(ParseState *pstate, List *schemaElts,
 				}
 				break;
 
+			case T_DefineStmt:
+				{
+					char	   *coll_schema = NULL;
+					char	   *collName;
+					char	   *obj_type;
+
+					DefineStmt *stmt = castNode(DefineStmt, element);
+
+					obj_type = asc_toupper(stringify_objtype(stmt->kind),
+										   strlen(stringify_objtype(stmt->kind)));
+
+					if (stmt->kind != OBJECT_COLLATION)
+						ereport(ERROR,
+								errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								errmsg("CREATE SCHEMA ... CREATE %s currently not supported", obj_type));
+
+					DeconstructQualifiedName(stmt->defnames, &coll_schema, &collName);
+
+					if (coll_schema && strcmp(coll_schema, schemaName) != 0)
+						ereport(ERROR,
+								errcode(ERRCODE_INVALID_SCHEMA_DEFINITION),
+								errmsg("CREATE %s specifies a schema (%s) different from the one being created (%s)",
+									   obj_type, coll_schema, schemaName));
+
+					elements = lappend(elements, element);
+				}
+				break;
+
+				/*
+				 * gram.y classifies these as DefineStmt as well; therefore,
+				 * we must explicitly raise an error for these cases.
+				 */
+			case T_CompositeTypeStmt:
+			case T_CreateEnumStmt:
+			case T_CreateRangeStmt:
+				ereport(ERROR,
+						errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						errmsg("CREATE SCHEMA ... CREATE TYPE currently not supported"));
+				break;
 			default:
 				elog(ERROR, "unrecognized node type: %d",
 					 (int) nodeTag(element));
diff --git a/src/include/catalog/objectaddress.h b/src/include/catalog/objectaddress.h
index 630434b73cf..96e6abc9ffd 100644
--- a/src/include/catalog/objectaddress.h
+++ b/src/include/catalog/objectaddress.h
@@ -79,6 +79,7 @@ extern char *getObjectDescription(const ObjectAddress *object,
 extern char *getObjectDescriptionOids(Oid classid, Oid objid);
 
 extern int	read_objtype_from_string(const char *objtype);
+extern const char *stringify_objtype(ObjectType objtype);
 extern char *getObjectTypeDescription(const ObjectAddress *object,
 									  bool missing_ok);
 extern char *getObjectIdentity(const ObjectAddress *object,
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 d73c4702051..2a905b28600 100644
--- a/src/test/modules/test_ddl_deparse/expected/create_schema.out
+++ b/src/test/modules/test_ddl_deparse/expected/create_schema.out
@@ -14,8 +14,10 @@ NOTICE:  schema "baz" already exists, skipping
 CREATE SCHEMA element_test
   CREATE TABLE foo (id int)
   CREATE VIEW bar AS SELECT * FROM foo
-  CREATE DOMAIN d1 AS INT;
+  CREATE DOMAIN d1 AS INT
+  CREATE COLLATION coll (LOCALE="C");
 NOTICE:  DDL test: type simple, tag CREATE SCHEMA
 NOTICE:  DDL test: type simple, tag CREATE TABLE
 NOTICE:  DDL test: type simple, tag CREATE VIEW
 NOTICE:  DDL test: type simple, tag CREATE DOMAIN
+NOTICE:  DDL test: type simple, tag CREATE COLLATION
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 57ada462070..9727408dc69 100644
--- a/src/test/modules/test_ddl_deparse/sql/create_schema.sql
+++ b/src/test/modules/test_ddl_deparse/sql/create_schema.sql
@@ -15,4 +15,5 @@ CREATE SCHEMA IF NOT EXISTS baz;
 CREATE SCHEMA element_test
   CREATE TABLE foo (id int)
   CREATE VIEW bar AS SELECT * FROM foo
-  CREATE DOMAIN d1 AS INT;
+  CREATE DOMAIN d1 AS INT
+  CREATE COLLATION coll (LOCALE="C");
\ No newline at end of file
diff --git a/src/test/regress/expected/collate.icu.utf8.out b/src/test/regress/expected/collate.icu.utf8.out
index 8023014fe63..7e54e43363f 100644
--- a/src/test/regress/expected/collate.icu.utf8.out
+++ b/src/test/regress/expected/collate.icu.utf8.out
@@ -1295,6 +1295,21 @@ DROP TABLE test7;
 CREATE COLLATION testcoll_rulesx (provider = icu, locale = '', rules = '!!wrong!!');
 NOTICE:  using standard form "und" for ICU locale ""
 ERROR:  could not open collator for locale "und" with rules "!!wrong!!": U_INVALID_FORMAT_ERROR
+--CREATE SCHEMA CREATE COLLATION
+CREATE SCHEMA regress_schema_4
+  CREATE COLLATION coll_icu_und FROM "und-x-icu"
+  CREATE TABLE tts(a TEXT COLLATE coll_icu_und);
+\dO regress_schema_4.*
+                                         List of collations
+      Schema      |     Name     | Provider | Collate | Ctype | Locale | ICU Rules | Deterministic? 
+------------------+--------------+----------+---------+-------+--------+-----------+----------------
+ regress_schema_4 | coll_icu_und | icu      |         |       | und    |           | yes
+(1 row)
+
+DROP SCHEMA regress_schema_4 CASCADE;
+NOTICE:  drop cascades to 2 other objects
+DETAIL:  drop cascades to collation regress_schema_4.coll_icu_und
+drop cascades to table regress_schema_4.tts
 -- nondeterministic collations
 CREATE COLLATION ctest_det (provider = icu, locale = '', deterministic = true);
 NOTICE:  using standard form "und" for ICU locale ""
diff --git a/src/test/regress/expected/create_schema.out b/src/test/regress/expected/create_schema.out
index 0533c29a311..3e061c7a0ef 100644
--- a/src/test/regress/expected/create_schema.out
+++ b/src/test/regress/expected/create_schema.out
@@ -177,6 +177,41 @@ CREATE SCHEMA regress_schema_3 AUTHORIZATION CURRENT_ROLE
  regress_schema_3 | ss1  | regress_schema_3.ss |           |          | 'hello'::text | 
 (2 rows)
 
+-- Cases where the schema creation with collations
+--fail. can not CREATE COLLATION to other schema
+CREATE SCHEMA regress_schema_4 AUTHORIZATION CURRENT_ROLE
+  CREATE COLLATION public.coll_icu_und FROM "und-x-icu";
+ERROR:  CREATE COLLATION specifies a schema (public) different from the one being created (regress_schema_4)
+--fail. improper qualified name
+CREATE SCHEMA regress_schema_4 AUTHORIZATION CURRENT_ROLE
+  CREATE COLLATION postgres.public.coll_icu_und FROM "und-x-icu";
+ERROR:  cross-database references are not implemented: postgres.public.coll_icu_und
+--fail. only support collation object for DefineStmt node
+CREATE SCHEMA regress_schema_4 AUTHORIZATION CURRENT_ROLE
+  CREATE AGGREGATE balk(int4) (SFUNC = int4_sum(int8, int4), STYPE = int8, PARALLEL = SAFE, INITCOND = '0');
+ERROR:  CREATE SCHEMA ... CREATE AGGREGATE currently not supported
+--ok, qualified schema name for collation should be same as the created schema
+CREATE SCHEMA regress_schema_4 AUTHORIZATION CURRENT_ROLE
+  CREATE COLLATION regress_schema_4.coll (LOCALE="C")
+  CREATE TABLE t(a TEXT COLLATE regress_schema_4.coll);
+\dO regress_schema_4.*
+                                     List of collations
+      Schema      | Name | Provider | Collate | Ctype | Locale | ICU Rules | Deterministic? 
+------------------+------+----------+---------+-------+--------+-----------+----------------
+ regress_schema_4 | coll | libc     | C       | C     |        |           | yes
+(1 row)
+
+--ok, no qualified schema name for collation
+CREATE SCHEMA regress_schema_5 AUTHORIZATION CURRENT_ROLE
+  CREATE COLLATION coll (LOCALE="C")
+  CREATE TABLE t(a TEXT COLLATE coll);
+\dO regress_schema_5.*
+                                     List of collations
+      Schema      | Name | Provider | Collate | Ctype | Locale | ICU Rules | Deterministic? 
+------------------+------+----------+---------+-------+--------+-----------+----------------
+ regress_schema_5 | coll | libc     | C       | C     |        |           | yes
+(1 row)
+
 DROP SCHEMA regress_schema_2 CASCADE;
 NOTICE:  drop cascades to 2 other objects
 DETAIL:  drop cascades to type regress_schema_2.ss
@@ -187,5 +222,13 @@ DETAIL:  drop cascades to type regress_schema_3.ss
 drop cascades to type regress_schema_3.ss1
 drop cascades to view regress_schema_3.test
 drop cascades to table regress_schema_3.t
+DROP SCHEMA regress_schema_4 CASCADE;
+NOTICE:  drop cascades to 2 other objects
+DETAIL:  drop cascades to collation regress_schema_4.coll
+drop cascades to table regress_schema_4.t
+DROP SCHEMA regress_schema_5 CASCADE;
+NOTICE:  drop cascades to 2 other objects
+DETAIL:  drop cascades to collation regress_schema_5.coll
+drop cascades to table regress_schema_5.t
 -- Clean up
 DROP ROLE regress_create_schema_role;
diff --git a/src/test/regress/sql/collate.icu.utf8.sql b/src/test/regress/sql/collate.icu.utf8.sql
index b6c54503d21..243d69e4d32 100644
--- a/src/test/regress/sql/collate.icu.utf8.sql
+++ b/src/test/regress/sql/collate.icu.utf8.sql
@@ -513,6 +513,13 @@ DROP TABLE test7;
 
 CREATE COLLATION testcoll_rulesx (provider = icu, locale = '', rules = '!!wrong!!');
 
+--CREATE SCHEMA CREATE COLLATION
+CREATE SCHEMA regress_schema_4
+  CREATE COLLATION coll_icu_und FROM "und-x-icu"
+  CREATE TABLE tts(a TEXT COLLATE coll_icu_und);
+\dO regress_schema_4.*
+DROP SCHEMA regress_schema_4 CASCADE;
+
 
 -- nondeterministic collations
 
diff --git a/src/test/regress/sql/create_schema.sql b/src/test/regress/sql/create_schema.sql
index 54a07054767..0f802bcaffe 100644
--- a/src/test/regress/sql/create_schema.sql
+++ b/src/test/regress/sql/create_schema.sql
@@ -104,8 +104,37 @@ CREATE SCHEMA regress_schema_3 AUTHORIZATION CURRENT_ROLE
   CREATE TABLE t(a ss1);
 \dD regress_schema_3.*
 
+-- Cases where the schema creation with collations
+--fail. can not CREATE COLLATION to other schema
+CREATE SCHEMA regress_schema_4 AUTHORIZATION CURRENT_ROLE
+  CREATE COLLATION public.coll_icu_und FROM "und-x-icu";
+
+--fail. improper qualified name
+CREATE SCHEMA regress_schema_4 AUTHORIZATION CURRENT_ROLE
+  CREATE COLLATION postgres.public.coll_icu_und FROM "und-x-icu";
+
+--fail. only support collation object for DefineStmt node
+CREATE SCHEMA regress_schema_4 AUTHORIZATION CURRENT_ROLE
+  CREATE AGGREGATE balk(int4) (SFUNC = int4_sum(int8, int4), STYPE = int8, PARALLEL = SAFE, INITCOND = '0');
+
+--ok, qualified schema name for collation should be same as the created schema
+CREATE SCHEMA regress_schema_4 AUTHORIZATION CURRENT_ROLE
+  CREATE COLLATION regress_schema_4.coll (LOCALE="C")
+  CREATE TABLE t(a TEXT COLLATE regress_schema_4.coll);
+
+\dO regress_schema_4.*
+
+--ok, no qualified schema name for collation
+CREATE SCHEMA regress_schema_5 AUTHORIZATION CURRENT_ROLE
+  CREATE COLLATION coll (LOCALE="C")
+  CREATE TABLE t(a TEXT COLLATE coll);
+\dO regress_schema_5.*
+
+
 DROP SCHEMA regress_schema_2 CASCADE;
 DROP SCHEMA regress_schema_3 CASCADE;
+DROP SCHEMA regress_schema_4 CASCADE;
+DROP SCHEMA regress_schema_5 CASCADE;
 
 -- Clean up
 DROP ROLE regress_create_schema_role;
-- 
2.34.1



^ permalink  raw  reply  [nested|flat] 32+ messages in thread

* Re: CREATE SCHEMA ... CREATE DOMAIN support
  2024-11-30 19:08 Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  2024-12-03 19:35 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Peter Eisentraut <[email protected]>
  2024-12-03 20:07   ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  2024-12-11 06:22     ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <[email protected]>
  2024-12-11 17:08       ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  2025-08-19 03:36         ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <[email protected]>
  2025-08-22 08:59           ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <[email protected]>
  2025-09-02 21:24             ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  2025-12-17 13:29               ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <[email protected]>
  2025-12-31 01:40                 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <[email protected]>
  2026-01-19 15:06                   ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <[email protected]>
@ 2026-03-10 14:11                     ` jian he <[email protected]>
  2026-03-30 02:44                       ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <[email protected]>
  0 siblings, 1 reply; 32+ messages in thread

From: jian he @ 2026-03-10 14:11 UTC (permalink / raw)
  To: Tom Lane <[email protected]>; +Cc: Kirill Reshke <[email protected]>; Peter Eisentraut <[email protected]>; PostgreSQL Hackers <[email protected]>

Hi.
Rebase only.



--
jian
https://www.enterprisedb.com/


Attachments:

  [text/x-patch] v10-0004-CREATE-SCHEMA-CREATE-TYPE.patch (15.9K, 2-v10-0004-CREATE-SCHEMA-CREATE-TYPE.patch)
  download | inline diff:
From 3136cddd6a8762aacbf6c9cbce84db2300ab0ebc Mon Sep 17 00:00:00 2001
From: jian he <[email protected]>
Date: Tue, 10 Mar 2026 17:56:16 +0800
Subject: [PATCH v10 4/5] CREATE SCHEMA CREATE TYPE

The SQL standard allows types to be specified in a CREATE SCHEMA statement.
This adds support for that capability.

For example:
CREATE SCHEMA schema_name CREATE TYPE ss;

The type will be created within the to be created schema.  The type name can be
schema-qualified or database-qualified, however it's not allowed to let type
create within a different schema.

Discussion: https://postgr.es/m/CALdSSPh4jUSDsWu3K58hjO60wnTRR0DuO4CKRcwa8EVuOSfXxg@mail.gmail.com
Commitfest: https://commitfest.postgresql.org/patch/5985
---
 doc/src/sgml/ref/create_schema.sgml           |  3 +-
 src/backend/parser/parse_utilcmd.c            | 52 ++++++++++--
 .../expected/create_schema.out                | 16 +++-
 .../test_ddl_deparse/sql/create_schema.sql    |  6 +-
 src/test/regress/expected/create_schema.out   | 83 +++++++++++++++++++
 src/test/regress/expected/create_type.out     | 12 +++
 src/test/regress/sql/create_schema.sql        | 39 +++++++++
 src/test/regress/sql/create_type.sql          | 12 +++
 8 files changed, 211 insertions(+), 12 deletions(-)

diff --git a/doc/src/sgml/ref/create_schema.sgml b/doc/src/sgml/ref/create_schema.sgml
index d8273bb2d0c..905e966e30e 100644
--- a/doc/src/sgml/ref/create_schema.sgml
+++ b/doc/src/sgml/ref/create_schema.sgml
@@ -104,7 +104,8 @@ CREATE SCHEMA IF NOT EXISTS AUTHORIZATION <replaceable class="parameter">role_sp
         <command>CREATE DOMAIN</command>
         <command>CREATE TABLE</command>, <command>CREATE VIEW</command>, <command>CREATE
         INDEX</command>, <command>CREATE SEQUENCE</command>, <command>CREATE
-        TRIGGER</command> and <command>GRANT</command> are accepted as clauses
+        TRIGGER</command>, <command>CREATE TYPE</command> and
+        <command>GRANT</command> are accepted as clauses
         within <command>CREATE SCHEMA</command>. Other kinds of objects may
         be created in separate commands after the schema is created.
        </para>
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 723a017a4cb..b6ee8342fde 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -4502,7 +4502,7 @@ transformCreateSchemaStmtElements(ParseState *pstate, List *schemaElts,
 					obj_type = asc_toupper(stringify_objtype(stmt->kind),
 										   strlen(stringify_objtype(stmt->kind)));
 
-					if (stmt->kind != OBJECT_COLLATION)
+					if (stmt->kind != OBJECT_COLLATION && stmt->kind != OBJECT_TYPE)
 						ereport(ERROR,
 								errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 								errmsg("CREATE SCHEMA ... CREATE %s currently not supported", obj_type));
@@ -4519,16 +4519,52 @@ transformCreateSchemaStmtElements(ParseState *pstate, List *schemaElts,
 				}
 				break;
 
-				/*
-				 * gram.y classifies these as DefineStmt as well; therefore,
-				 * we must explicitly raise an error for these cases.
-				 */
 			case T_CompositeTypeStmt:
+				{
+					CompositeTypeStmt *stmt = castNode(CompositeTypeStmt, element);
+
+					checkSchemaName(pstate, schemaName, stmt->typevar);
+
+					elements = lappend(elements, element);
+				}
+				break;
+
 			case T_CreateEnumStmt:
+				{
+					char	   *typschema = NULL;
+					char	   *typname;
+
+					CreateEnumStmt *stmt = castNode(CreateEnumStmt, element);
+
+					DeconstructQualifiedName(stmt->typeName, &typschema, &typname);
+
+					if (typschema && strcmp(typschema, schemaName) != 0)
+						ereport(ERROR,
+								errcode(ERRCODE_INVALID_SCHEMA_DEFINITION),
+								errmsg("CREATE TYPE specifies a schema (%s) different from the one being created (%s)",
+									   typschema, schemaName));
+
+					elements = lappend(elements, element);
+				}
+				break;
+
 			case T_CreateRangeStmt:
-				ereport(ERROR,
-						errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-						errmsg("CREATE SCHEMA ... CREATE TYPE currently not supported"));
+				{
+					char	   *typschema = NULL;
+					char	   *typname;
+
+					CreateRangeStmt *stmt = castNode(CreateRangeStmt, element);
+
+					DeconstructQualifiedName(stmt->typeName, &typschema, &typname);
+
+					if (typschema && strcmp(typschema, schemaName) != 0)
+						ereport(ERROR,
+								errcode(ERRCODE_INVALID_SCHEMA_DEFINITION),
+								errmsg("CREATE TYPE specifies a schema (%s) different from the one being created (%s)",
+									   schemaName, typschema));
+
+					elements = lappend(elements, element);
+				}
 				break;
 			default:
 				elog(ERROR, "unrecognized node type: %d",
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 e3c197af118..92da27c813b 100644
--- a/src/test/modules/test_ddl_deparse/expected/create_schema.out
+++ b/src/test/modules/test_ddl_deparse/expected/create_schema.out
@@ -15,15 +15,27 @@ CREATE SCHEMA element_test
   CREATE TABLE foo (id int)
   CREATE VIEW bar AS SELECT * FROM foo
   CREATE DOMAIN d1 AS INT
-  CREATE COLLATION coll (LOCALE="C");
+  CREATE COLLATION coll (LOCALE="C")
+  CREATE TYPE floatrange AS RANGE (subtype = float8, subtype_diff = float8mi)
+  CREATE TYPE ss AS (a int)
+  CREATE TYPE sss
+  CREATE TYPE rainbow AS ENUM ('red', 'orange');
 NOTICE:  DDL test: type simple, tag CREATE SCHEMA
 NOTICE:  DDL test: type simple, tag CREATE TABLE
 NOTICE:  DDL test: type simple, tag CREATE VIEW
 NOTICE:  DDL test: type simple, tag CREATE DOMAIN
 NOTICE:  DDL test: type simple, tag CREATE COLLATION
+NOTICE:  DDL test: type simple, tag CREATE TYPE
+NOTICE:  DDL test: type simple, tag CREATE TYPE
+NOTICE:  DDL test: type simple, tag CREATE TYPE
+NOTICE:  DDL test: type simple, tag CREATE TYPE
 DROP SCHEMA element_test CASCADE;
-NOTICE:  drop cascades to 4 other objects
+NOTICE:  drop cascades to 8 other objects
 DETAIL:  drop cascades to table element_test.foo
 drop cascades to view element_test.bar
 drop cascades to type element_test.d1
 drop cascades to collation element_test.coll
+drop cascades to type element_test.floatrange
+drop cascades to type element_test.ss
+drop cascades to type element_test.sss
+drop cascades to type element_test.rainbow
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 0c61db13935..abe0637c415 100644
--- a/src/test/modules/test_ddl_deparse/sql/create_schema.sql
+++ b/src/test/modules/test_ddl_deparse/sql/create_schema.sql
@@ -16,6 +16,10 @@ CREATE SCHEMA element_test
   CREATE TABLE foo (id int)
   CREATE VIEW bar AS SELECT * FROM foo
   CREATE DOMAIN d1 AS INT
-  CREATE COLLATION coll (LOCALE="C");
+  CREATE COLLATION coll (LOCALE="C")
+  CREATE TYPE floatrange AS RANGE (subtype = float8, subtype_diff = float8mi)
+  CREATE TYPE ss AS (a int)
+  CREATE TYPE sss
+  CREATE TYPE rainbow AS ENUM ('red', 'orange');
 
 DROP SCHEMA element_test CASCADE;
diff --git a/src/test/regress/expected/create_schema.out b/src/test/regress/expected/create_schema.out
index 3e061c7a0ef..a33cbd0c0d9 100644
--- a/src/test/regress/expected/create_schema.out
+++ b/src/test/regress/expected/create_schema.out
@@ -212,6 +212,76 @@ CREATE SCHEMA regress_schema_5 AUTHORIZATION CURRENT_ROLE
  regress_schema_5 | coll | libc     | C       | C     |        |           | yes
 (1 row)
 
+-----CREATE SCHEMA CREATE TYPE
+--fail. can not CREATE TYPE to other schema
+CREATE SCHEMA regress_schema_6 CREATE TYPE public.ss AS (a int);
+ERROR:  CREATE specifies a schema (public) different from the one being created (regress_schema_6)
+LINE 1: CREATE SCHEMA regress_schema_6 CREATE TYPE public.ss AS (a i...
+                                                   ^
+CREATE SCHEMA regress_schema_6 CREATE TYPE public.ss;
+ERROR:  CREATE TYPE specifies a schema (public) different from the one being created (regress_schema_6)
+CREATE SCHEMA regress_schema_6 CREATE TYPE public.rainbow AS ENUM ('red', 'orange');
+ERROR:  CREATE TYPE specifies a schema (public) different from the one being created (regress_schema_6)
+CREATE SCHEMA regress_schema_6 CREATE TYPE public.floatrange AS RANGE (subtype = float8, subtype_diff = float8mi);
+ERROR:  CREATE TYPE specifies a schema (regress_schema_6) different from the one being created (public)
+--fail. improper qualified name
+CREATE SCHEMA regress_schema_6 AUTHORIZATION CURRENT_ROLE
+  CREATE TYPE postgres.public.floatrange AS RANGE (subtype = float8, subtype_diff = float8mi);
+ERROR:  cross-database references are not implemented: postgres.public.floatrange
+--the following tests should error, because we execute subcommands in order
+CREATE SCHEMA regress_schema_6 CREATE TABLE tts(a ss) CREATE TYPE ss;
+ERROR:  type "ss" does not exist
+LINE 1: CREATE SCHEMA regress_schema_6 CREATE TABLE tts(a ss) CREATE...
+                                                          ^
+CREATE SCHEMA regress_schema_6 CREATE TABLE tts(a ss) CREATE TYPE ss AS (a int);
+ERROR:  type "ss" does not exist
+LINE 1: CREATE SCHEMA regress_schema_6 CREATE TABLE tts(a ss) CREATE...
+                                                          ^
+CREATE SCHEMA regress_schema_6 CREATE TABLE tts(a rainbow) CREATE TYPE rainbow AS ENUM ('red', 'orange');
+ERROR:  type "rainbow" does not exist
+LINE 1: CREATE SCHEMA regress_schema_6 CREATE TABLE tts(a rainbow) C...
+                                                          ^
+CREATE SCHEMA regress_schema_6
+  CREATE TABLE tts(a floatrange)
+  CREATE TYPE floatrange AS RANGE (subtype = float8, subtype_diff = float8mi);
+ERROR:  type "floatrange" does not exist
+LINE 2:   CREATE TABLE tts(a floatrange)
+                             ^
+--ok
+CREATE SCHEMA regress_schema_6
+  CREATE TYPE floatrange AS RANGE (subtype = float8, subtype_diff = float8mi)
+  CREATE TYPE ss AS (a int)
+  CREATE TYPE sss
+  CREATE TYPE rainbow AS ENUM ('red', 'orange');
+\dT regress_schema_6.*
+                        List of data types
+      Schema      |               Name               | Description 
+------------------+----------------------------------+-------------
+ regress_schema_6 | regress_schema_6.floatmultirange | 
+ regress_schema_6 | regress_schema_6.floatrange      | 
+ regress_schema_6 | regress_schema_6.rainbow         | 
+ regress_schema_6 | regress_schema_6.ss              | 
+ regress_schema_6 | regress_schema_6.sss             | 
+(5 rows)
+
+--schema qualified, ok
+CREATE SCHEMA regress_schema_7
+  CREATE TYPE regress_schema_7.floatrange AS RANGE (subtype = float8, subtype_diff = float8mi)
+  CREATE TYPE regress_schema_7.ss AS (a int)
+  CREATE TYPE regress_schema_7.sss
+  CREATE TYPE regress_schema_7.rainbow AS ENUM ('red', 'orange')
+  CREATE TABLE t(a floatrange, b ss, c rainbow);
+\dT regress_schema_7.*
+                        List of data types
+      Schema      |               Name               | Description 
+------------------+----------------------------------+-------------
+ regress_schema_7 | regress_schema_7.floatmultirange | 
+ regress_schema_7 | regress_schema_7.floatrange      | 
+ regress_schema_7 | regress_schema_7.rainbow         | 
+ regress_schema_7 | regress_schema_7.ss              | 
+ regress_schema_7 | regress_schema_7.sss             | 
+(5 rows)
+
 DROP SCHEMA regress_schema_2 CASCADE;
 NOTICE:  drop cascades to 2 other objects
 DETAIL:  drop cascades to type regress_schema_2.ss
@@ -230,5 +300,18 @@ DROP SCHEMA regress_schema_5 CASCADE;
 NOTICE:  drop cascades to 2 other objects
 DETAIL:  drop cascades to collation regress_schema_5.coll
 drop cascades to table regress_schema_5.t
+DROP SCHEMA regress_schema_6 CASCADE;
+NOTICE:  drop cascades to 4 other objects
+DETAIL:  drop cascades to type regress_schema_6.floatrange
+drop cascades to type regress_schema_6.ss
+drop cascades to type regress_schema_6.sss
+drop cascades to type regress_schema_6.rainbow
+DROP SCHEMA regress_schema_7 CASCADE;
+NOTICE:  drop cascades to 5 other objects
+DETAIL:  drop cascades to type regress_schema_7.floatrange
+drop cascades to type regress_schema_7.ss
+drop cascades to type regress_schema_7.sss
+drop cascades to type regress_schema_7.rainbow
+drop cascades to table regress_schema_7.t
 -- Clean up
 DROP ROLE regress_create_schema_role;
diff --git a/src/test/regress/expected/create_type.out b/src/test/regress/expected/create_type.out
index 5181c4290b4..83f63e5c8cc 100644
--- a/src/test/regress/expected/create_type.out
+++ b/src/test/regress/expected/create_type.out
@@ -35,6 +35,18 @@ CREATE FUNCTION int44out(city_budget)
 NOTICE:  argument type city_budget is only a shell
 LINE 1: CREATE FUNCTION int44out(city_budget)
                                  ^
+----- CREATE SCHEMA CREATE TYPE
+--error, cannot CREATE TYPE to other schema
+CREATE SCHEMA regress_schema_x
+   CREATE TYPE public.widget (
+      internallength = 24,
+      input = widget_in,
+      output = widget_out,
+      typmod_in = numerictypmodin,
+      typmod_out = numerictypmodout,
+      alignment = double
+   );
+ERROR:  CREATE TYPE specifies a schema (public) different from the one being created (regress_schema_x)
 CREATE TYPE widget (
    internallength = 24,
    input = widget_in,
diff --git a/src/test/regress/sql/create_schema.sql b/src/test/regress/sql/create_schema.sql
index 0f802bcaffe..75fd928eacb 100644
--- a/src/test/regress/sql/create_schema.sql
+++ b/src/test/regress/sql/create_schema.sql
@@ -131,10 +131,49 @@ CREATE SCHEMA regress_schema_5 AUTHORIZATION CURRENT_ROLE
 \dO regress_schema_5.*
 
 
+
+-----CREATE SCHEMA CREATE TYPE
+--fail. can not CREATE TYPE to other schema
+CREATE SCHEMA regress_schema_6 CREATE TYPE public.ss AS (a int);
+CREATE SCHEMA regress_schema_6 CREATE TYPE public.ss;
+CREATE SCHEMA regress_schema_6 CREATE TYPE public.rainbow AS ENUM ('red', 'orange');
+CREATE SCHEMA regress_schema_6 CREATE TYPE public.floatrange AS RANGE (subtype = float8, subtype_diff = float8mi);
+
+--fail. improper qualified name
+CREATE SCHEMA regress_schema_6 AUTHORIZATION CURRENT_ROLE
+  CREATE TYPE postgres.public.floatrange AS RANGE (subtype = float8, subtype_diff = float8mi);
+
+--the following tests should error, because we execute subcommands in order
+CREATE SCHEMA regress_schema_6 CREATE TABLE tts(a ss) CREATE TYPE ss;
+CREATE SCHEMA regress_schema_6 CREATE TABLE tts(a ss) CREATE TYPE ss AS (a int);
+CREATE SCHEMA regress_schema_6 CREATE TABLE tts(a rainbow) CREATE TYPE rainbow AS ENUM ('red', 'orange');
+CREATE SCHEMA regress_schema_6
+  CREATE TABLE tts(a floatrange)
+  CREATE TYPE floatrange AS RANGE (subtype = float8, subtype_diff = float8mi);
+
+--ok
+CREATE SCHEMA regress_schema_6
+  CREATE TYPE floatrange AS RANGE (subtype = float8, subtype_diff = float8mi)
+  CREATE TYPE ss AS (a int)
+  CREATE TYPE sss
+  CREATE TYPE rainbow AS ENUM ('red', 'orange');
+\dT regress_schema_6.*
+
+--schema qualified, ok
+CREATE SCHEMA regress_schema_7
+  CREATE TYPE regress_schema_7.floatrange AS RANGE (subtype = float8, subtype_diff = float8mi)
+  CREATE TYPE regress_schema_7.ss AS (a int)
+  CREATE TYPE regress_schema_7.sss
+  CREATE TYPE regress_schema_7.rainbow AS ENUM ('red', 'orange')
+  CREATE TABLE t(a floatrange, b ss, c rainbow);
+\dT regress_schema_7.*
+
 DROP SCHEMA regress_schema_2 CASCADE;
 DROP SCHEMA regress_schema_3 CASCADE;
 DROP SCHEMA regress_schema_4 CASCADE;
 DROP SCHEMA regress_schema_5 CASCADE;
+DROP SCHEMA regress_schema_6 CASCADE;
+DROP SCHEMA regress_schema_7 CASCADE;
 
 -- Clean up
 DROP ROLE regress_create_schema_role;
diff --git a/src/test/regress/sql/create_type.sql b/src/test/regress/sql/create_type.sql
index c25018029c2..69efd069f58 100644
--- a/src/test/regress/sql/create_type.sql
+++ b/src/test/regress/sql/create_type.sql
@@ -32,6 +32,18 @@ CREATE FUNCTION int44out(city_budget)
    AS :'regresslib'
    LANGUAGE C STRICT IMMUTABLE;
 
+----- CREATE SCHEMA CREATE TYPE
+--error, cannot CREATE TYPE to other schema
+CREATE SCHEMA regress_schema_x
+   CREATE TYPE public.widget (
+      internallength = 24,
+      input = widget_in,
+      output = widget_out,
+      typmod_in = numerictypmodin,
+      typmod_out = numerictypmodout,
+      alignment = double
+   );
+
 CREATE TYPE widget (
    internallength = 24,
    input = widget_in,
-- 
2.34.1



  [text/x-patch] v10-0003-CREATE-SCHEMA-CREATE-COLLATION.patch (14.3K, 3-v10-0003-CREATE-SCHEMA-CREATE-COLLATION.patch)
  download | inline diff:
From 1a255ed67ae9a020c20ad0c3297b91e40ec7ddd0 Mon Sep 17 00:00:00 2001
From: jian he <[email protected]>
Date: Tue, 10 Mar 2026 17:53:47 +0800
Subject: [PATCH v10 3/5] CREATE SCHEMA CREATE COLLATION

The SQL standard allows collation to be specified in a CREATE SCHEMA statement.
This adds support for that capability.

For example:
    CREATE SCHEMA schema_name CREATE COLLATION coll_icu_und FROM "und-x-icu";

The collation will be created within the to be created schema.  The collation
name can be schema-qualified or database-qualified, however it's not allowed to
let collation create within a different schema.

Discussion: https://postgr.es/m/CALdSSPh4jUSDsWu3K58hjO60wnTRR0DuO4CKRcwa8EVuOSfXxg@mail.gmail.com
Commitfest: https://commitfest.postgresql.org/patch/5985
---
 doc/src/sgml/ref/create_schema.sgml           |  3 +-
 src/backend/catalog/objectaddress.c           | 18 ++++++++
 src/backend/parser/gram.y                     |  1 +
 src/backend/parser/parse_utilcmd.c            | 40 +++++++++++++++++
 src/include/catalog/objectaddress.h           |  1 +
 .../expected/create_schema.out                |  7 ++-
 .../test_ddl_deparse/sql/create_schema.sql    |  3 +-
 .../regress/expected/collate.icu.utf8.out     | 15 +++++++
 src/test/regress/expected/create_schema.out   | 43 +++++++++++++++++++
 src/test/regress/sql/collate.icu.utf8.sql     |  7 +++
 src/test/regress/sql/create_schema.sql        | 29 +++++++++++++
 11 files changed, 163 insertions(+), 4 deletions(-)

diff --git a/doc/src/sgml/ref/create_schema.sgml b/doc/src/sgml/ref/create_schema.sgml
index 79186d2b936..d8273bb2d0c 100644
--- a/doc/src/sgml/ref/create_schema.sgml
+++ b/doc/src/sgml/ref/create_schema.sgml
@@ -100,7 +100,8 @@ CREATE SCHEMA IF NOT EXISTS AUTHORIZATION <replaceable class="parameter">role_sp
       <listitem>
        <para>
         An SQL statement defining an object to be created within the
-        schema. Currently, only <command>CREATE DOMAIN</command>
+        schema. Currently, only <command>CREATE COLLATION</command>,
+        <command>CREATE DOMAIN</command>
         <command>CREATE TABLE</command>, <command>CREATE VIEW</command>, <command>CREATE
         INDEX</command>, <command>CREATE SEQUENCE</command>, <command>CREATE
         TRIGGER</command> and <command>GRANT</command> are accepted as clauses
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index d32aaff2821..9bd5c76c016 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -2622,6 +2622,24 @@ read_objtype_from_string(const char *objtype)
 	return -1;					/* keep compiler quiet */
 }
 
+/* get the ObjectType name */
+const char *
+stringify_objtype(ObjectType objtype)
+{
+	for (int i = 0; i < lengthof(ObjectTypeMap); i++)
+	{
+		if (ObjectTypeMap[i].tm_type == objtype)
+			return ObjectTypeMap[i].tm_name;
+	}
+
+	ereport(ERROR,
+			errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+			errmsg("unrecognized object type %d", objtype));
+
+	return NULL;				/* keep compiler quiet */
+}
+
+
 /*
  * Interfaces to reference fields of ObjectPropertyType
  */
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 1a9b3759376..e867d1d5661 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -1641,6 +1641,7 @@ schema_stmt:
 			| GrantStmt
 			| ViewStmt
 			| CreateDomainStmt
+			| DefineStmt
 		;
 
 
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 9b4a209378d..723a017a4cb 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -64,6 +64,7 @@
 #include "rewrite/rewriteManip.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
+#include "utils/formatting.h"
 #include "utils/lsyscache.h"
 #include "utils/partcache.h"
 #include "utils/rel.h"
@@ -4490,6 +4491,45 @@ transformCreateSchemaStmtElements(ParseState *pstate, List *schemaElts,
 				}
 				break;
 
+			case T_DefineStmt:
+				{
+					char	   *coll_schema = NULL;
+					char	   *collName;
+					char	   *obj_type;
+
+					DefineStmt *stmt = castNode(DefineStmt, element);
+
+					obj_type = asc_toupper(stringify_objtype(stmt->kind),
+										   strlen(stringify_objtype(stmt->kind)));
+
+					if (stmt->kind != OBJECT_COLLATION)
+						ereport(ERROR,
+								errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								errmsg("CREATE SCHEMA ... CREATE %s currently not supported", obj_type));
+
+					DeconstructQualifiedName(stmt->defnames, &coll_schema, &collName);
+
+					if (coll_schema && strcmp(coll_schema, schemaName) != 0)
+						ereport(ERROR,
+								errcode(ERRCODE_INVALID_SCHEMA_DEFINITION),
+								errmsg("CREATE %s specifies a schema (%s) different from the one being created (%s)",
+									   obj_type, coll_schema, schemaName));
+
+					elements = lappend(elements, element);
+				}
+				break;
+
+				/*
+				 * gram.y classifies these as DefineStmt as well; therefore,
+				 * we must explicitly raise an error for these cases.
+				 */
+			case T_CompositeTypeStmt:
+			case T_CreateEnumStmt:
+			case T_CreateRangeStmt:
+				ereport(ERROR,
+						errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						errmsg("CREATE SCHEMA ... CREATE TYPE currently not supported"));
+				break;
 			default:
 				elog(ERROR, "unrecognized node type: %d",
 					 (int) nodeTag(element));
diff --git a/src/include/catalog/objectaddress.h b/src/include/catalog/objectaddress.h
index b549be2d523..20d52ee589a 100644
--- a/src/include/catalog/objectaddress.h
+++ b/src/include/catalog/objectaddress.h
@@ -80,6 +80,7 @@ extern char *getObjectDescription(const ObjectAddress *object,
 extern char *getObjectDescriptionOids(Oid classid, Oid objid);
 
 extern int	read_objtype_from_string(const char *objtype);
+extern const char *stringify_objtype(ObjectType objtype);
 extern char *getObjectTypeDescription(const ObjectAddress *object,
 									  bool missing_ok);
 extern char *getObjectIdentity(const ObjectAddress *object,
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 634900d8456..e3c197af118 100644
--- a/src/test/modules/test_ddl_deparse/expected/create_schema.out
+++ b/src/test/modules/test_ddl_deparse/expected/create_schema.out
@@ -14,13 +14,16 @@ NOTICE:  schema "baz" already exists, skipping
 CREATE SCHEMA element_test
   CREATE TABLE foo (id int)
   CREATE VIEW bar AS SELECT * FROM foo
-  CREATE DOMAIN d1 AS INT;
+  CREATE DOMAIN d1 AS INT
+  CREATE COLLATION coll (LOCALE="C");
 NOTICE:  DDL test: type simple, tag CREATE SCHEMA
 NOTICE:  DDL test: type simple, tag CREATE TABLE
 NOTICE:  DDL test: type simple, tag CREATE VIEW
 NOTICE:  DDL test: type simple, tag CREATE DOMAIN
+NOTICE:  DDL test: type simple, tag CREATE COLLATION
 DROP SCHEMA element_test CASCADE;
-NOTICE:  drop cascades to 3 other objects
+NOTICE:  drop cascades to 4 other objects
 DETAIL:  drop cascades to table element_test.foo
 drop cascades to view element_test.bar
 drop cascades to type element_test.d1
+drop cascades to collation element_test.coll
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 b822517b64d..0c61db13935 100644
--- a/src/test/modules/test_ddl_deparse/sql/create_schema.sql
+++ b/src/test/modules/test_ddl_deparse/sql/create_schema.sql
@@ -15,6 +15,7 @@ CREATE SCHEMA IF NOT EXISTS baz;
 CREATE SCHEMA element_test
   CREATE TABLE foo (id int)
   CREATE VIEW bar AS SELECT * FROM foo
-  CREATE DOMAIN d1 AS INT;
+  CREATE DOMAIN d1 AS INT
+  CREATE COLLATION coll (LOCALE="C");
 
 DROP SCHEMA element_test CASCADE;
diff --git a/src/test/regress/expected/collate.icu.utf8.out b/src/test/regress/expected/collate.icu.utf8.out
index 1325e123877..fdd4439674f 100644
--- a/src/test/regress/expected/collate.icu.utf8.out
+++ b/src/test/regress/expected/collate.icu.utf8.out
@@ -1297,6 +1297,21 @@ DROP TABLE test7;
 CREATE COLLATION testcoll_rulesx (provider = icu, locale = '', rules = '!!wrong!!');
 NOTICE:  using standard form "und" for ICU locale ""
 ERROR:  could not open collator for locale "und" with rules "!!wrong!!": U_INVALID_FORMAT_ERROR
+--CREATE SCHEMA CREATE COLLATION
+CREATE SCHEMA regress_schema_4
+  CREATE COLLATION coll_icu_und FROM "und-x-icu"
+  CREATE TABLE tts(a TEXT COLLATE coll_icu_und);
+\dO regress_schema_4.*
+                                         List of collations
+      Schema      |     Name     | Provider | Collate | Ctype | Locale | ICU Rules | Deterministic? 
+------------------+--------------+----------+---------+-------+--------+-----------+----------------
+ regress_schema_4 | coll_icu_und | icu      |         |       | und    |           | yes
+(1 row)
+
+DROP SCHEMA regress_schema_4 CASCADE;
+NOTICE:  drop cascades to 2 other objects
+DETAIL:  drop cascades to collation regress_schema_4.coll_icu_und
+drop cascades to table regress_schema_4.tts
 -- nondeterministic collations
 CREATE COLLATION ctest_det (provider = icu, locale = '', deterministic = true);
 NOTICE:  using standard form "und" for ICU locale ""
diff --git a/src/test/regress/expected/create_schema.out b/src/test/regress/expected/create_schema.out
index 0533c29a311..3e061c7a0ef 100644
--- a/src/test/regress/expected/create_schema.out
+++ b/src/test/regress/expected/create_schema.out
@@ -177,6 +177,41 @@ CREATE SCHEMA regress_schema_3 AUTHORIZATION CURRENT_ROLE
  regress_schema_3 | ss1  | regress_schema_3.ss |           |          | 'hello'::text | 
 (2 rows)
 
+-- Cases where the schema creation with collations
+--fail. can not CREATE COLLATION to other schema
+CREATE SCHEMA regress_schema_4 AUTHORIZATION CURRENT_ROLE
+  CREATE COLLATION public.coll_icu_und FROM "und-x-icu";
+ERROR:  CREATE COLLATION specifies a schema (public) different from the one being created (regress_schema_4)
+--fail. improper qualified name
+CREATE SCHEMA regress_schema_4 AUTHORIZATION CURRENT_ROLE
+  CREATE COLLATION postgres.public.coll_icu_und FROM "und-x-icu";
+ERROR:  cross-database references are not implemented: postgres.public.coll_icu_und
+--fail. only support collation object for DefineStmt node
+CREATE SCHEMA regress_schema_4 AUTHORIZATION CURRENT_ROLE
+  CREATE AGGREGATE balk(int4) (SFUNC = int4_sum(int8, int4), STYPE = int8, PARALLEL = SAFE, INITCOND = '0');
+ERROR:  CREATE SCHEMA ... CREATE AGGREGATE currently not supported
+--ok, qualified schema name for collation should be same as the created schema
+CREATE SCHEMA regress_schema_4 AUTHORIZATION CURRENT_ROLE
+  CREATE COLLATION regress_schema_4.coll (LOCALE="C")
+  CREATE TABLE t(a TEXT COLLATE regress_schema_4.coll);
+\dO regress_schema_4.*
+                                     List of collations
+      Schema      | Name | Provider | Collate | Ctype | Locale | ICU Rules | Deterministic? 
+------------------+------+----------+---------+-------+--------+-----------+----------------
+ regress_schema_4 | coll | libc     | C       | C     |        |           | yes
+(1 row)
+
+--ok, no qualified schema name for collation
+CREATE SCHEMA regress_schema_5 AUTHORIZATION CURRENT_ROLE
+  CREATE COLLATION coll (LOCALE="C")
+  CREATE TABLE t(a TEXT COLLATE coll);
+\dO regress_schema_5.*
+                                     List of collations
+      Schema      | Name | Provider | Collate | Ctype | Locale | ICU Rules | Deterministic? 
+------------------+------+----------+---------+-------+--------+-----------+----------------
+ regress_schema_5 | coll | libc     | C       | C     |        |           | yes
+(1 row)
+
 DROP SCHEMA regress_schema_2 CASCADE;
 NOTICE:  drop cascades to 2 other objects
 DETAIL:  drop cascades to type regress_schema_2.ss
@@ -187,5 +222,13 @@ DETAIL:  drop cascades to type regress_schema_3.ss
 drop cascades to type regress_schema_3.ss1
 drop cascades to view regress_schema_3.test
 drop cascades to table regress_schema_3.t
+DROP SCHEMA regress_schema_4 CASCADE;
+NOTICE:  drop cascades to 2 other objects
+DETAIL:  drop cascades to collation regress_schema_4.coll
+drop cascades to table regress_schema_4.t
+DROP SCHEMA regress_schema_5 CASCADE;
+NOTICE:  drop cascades to 2 other objects
+DETAIL:  drop cascades to collation regress_schema_5.coll
+drop cascades to table regress_schema_5.t
 -- Clean up
 DROP ROLE regress_create_schema_role;
diff --git a/src/test/regress/sql/collate.icu.utf8.sql b/src/test/regress/sql/collate.icu.utf8.sql
index b6c54503d21..243d69e4d32 100644
--- a/src/test/regress/sql/collate.icu.utf8.sql
+++ b/src/test/regress/sql/collate.icu.utf8.sql
@@ -513,6 +513,13 @@ DROP TABLE test7;
 
 CREATE COLLATION testcoll_rulesx (provider = icu, locale = '', rules = '!!wrong!!');
 
+--CREATE SCHEMA CREATE COLLATION
+CREATE SCHEMA regress_schema_4
+  CREATE COLLATION coll_icu_und FROM "und-x-icu"
+  CREATE TABLE tts(a TEXT COLLATE coll_icu_und);
+\dO regress_schema_4.*
+DROP SCHEMA regress_schema_4 CASCADE;
+
 
 -- nondeterministic collations
 
diff --git a/src/test/regress/sql/create_schema.sql b/src/test/regress/sql/create_schema.sql
index 54a07054767..0f802bcaffe 100644
--- a/src/test/regress/sql/create_schema.sql
+++ b/src/test/regress/sql/create_schema.sql
@@ -104,8 +104,37 @@ CREATE SCHEMA regress_schema_3 AUTHORIZATION CURRENT_ROLE
   CREATE TABLE t(a ss1);
 \dD regress_schema_3.*
 
+-- Cases where the schema creation with collations
+--fail. can not CREATE COLLATION to other schema
+CREATE SCHEMA regress_schema_4 AUTHORIZATION CURRENT_ROLE
+  CREATE COLLATION public.coll_icu_und FROM "und-x-icu";
+
+--fail. improper qualified name
+CREATE SCHEMA regress_schema_4 AUTHORIZATION CURRENT_ROLE
+  CREATE COLLATION postgres.public.coll_icu_und FROM "und-x-icu";
+
+--fail. only support collation object for DefineStmt node
+CREATE SCHEMA regress_schema_4 AUTHORIZATION CURRENT_ROLE
+  CREATE AGGREGATE balk(int4) (SFUNC = int4_sum(int8, int4), STYPE = int8, PARALLEL = SAFE, INITCOND = '0');
+
+--ok, qualified schema name for collation should be same as the created schema
+CREATE SCHEMA regress_schema_4 AUTHORIZATION CURRENT_ROLE
+  CREATE COLLATION regress_schema_4.coll (LOCALE="C")
+  CREATE TABLE t(a TEXT COLLATE regress_schema_4.coll);
+
+\dO regress_schema_4.*
+
+--ok, no qualified schema name for collation
+CREATE SCHEMA regress_schema_5 AUTHORIZATION CURRENT_ROLE
+  CREATE COLLATION coll (LOCALE="C")
+  CREATE TABLE t(a TEXT COLLATE coll);
+\dO regress_schema_5.*
+
+
 DROP SCHEMA regress_schema_2 CASCADE;
 DROP SCHEMA regress_schema_3 CASCADE;
+DROP SCHEMA regress_schema_4 CASCADE;
+DROP SCHEMA regress_schema_5 CASCADE;
 
 -- Clean up
 DROP ROLE regress_create_schema_role;
-- 
2.34.1



  [text/x-patch] v10-0002-CREATE-SCHEMA-CREATE-DOMAIN.patch (12.6K, 4-v10-0002-CREATE-SCHEMA-CREATE-DOMAIN.patch)
  download | inline diff:
From 42c983e59f77197d99a584f7f7d63446fffa0752 Mon Sep 17 00:00:00 2001
From: jian he <[email protected]>
Date: Tue, 10 Mar 2026 17:51:02 +0800
Subject: [PATCH v10 2/5] CREATE SCHEMA CREATE DOMAIN

The SQL standard allows domains to be specified in a CREATE SCHEMA statement.
This adds support for that capability.

For example:
    CREATE SCHEMA schema_name AUTHORIZATION CURRENT_ROLE
    create domain ss as text not null;

The domain will be created within the to be created schema.  The domain name can
be schema-qualified or database-qualified, however it's not allowed to let
domain create within a different schema.

Author: Kirill Reshke <[email protected]>
Author: Jian He <[email protected]>
Reviewed-by: Alvaro Herrera <[email protected]>
Reviewed-by: Tom Lane <[email protected]>

Discussion: https://postgr.es/m/CALdSSPh4jUSDsWu3K58hjO60wnTRR0DuO4CKRcwa8EVuOSfXxg@mail.gmail.com
Commitfest: https://commitfest.postgresql.org/patch/5985
---
 doc/src/sgml/ref/create_schema.sgml           |  4 +-
 src/backend/parser/gram.y                     |  1 +
 src/backend/parser/parse_utilcmd.c            | 26 +++++++++
 src/bin/psql/tab-complete.in.c                | 12 ++--
 .../expected/create_schema.out                |  9 ++-
 .../test_ddl_deparse/sql/create_schema.sql    |  5 +-
 src/test/regress/expected/create_schema.out   | 56 +++++++++++++++++++
 src/test/regress/sql/create_schema.sql        | 36 ++++++++++++
 8 files changed, 139 insertions(+), 10 deletions(-)

diff --git a/doc/src/sgml/ref/create_schema.sgml b/doc/src/sgml/ref/create_schema.sgml
index 625793a6b67..79186d2b936 100644
--- a/doc/src/sgml/ref/create_schema.sgml
+++ b/doc/src/sgml/ref/create_schema.sgml
@@ -100,8 +100,8 @@ CREATE SCHEMA IF NOT EXISTS AUTHORIZATION <replaceable class="parameter">role_sp
       <listitem>
        <para>
         An SQL statement defining an object to be created within the
-        schema. Currently, only <command>CREATE
-        TABLE</command>, <command>CREATE VIEW</command>, <command>CREATE
+        schema. Currently, only <command>CREATE DOMAIN</command>
+        <command>CREATE TABLE</command>, <command>CREATE VIEW</command>, <command>CREATE
         INDEX</command>, <command>CREATE SEQUENCE</command>, <command>CREATE
         TRIGGER</command> and <command>GRANT</command> are accepted as clauses
         within <command>CREATE SCHEMA</command>. Other kinds of objects may
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 9cbe8eafc45..1a9b3759376 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -1640,6 +1640,7 @@ schema_stmt:
 			| CreateTrigStmt
 			| GrantStmt
 			| ViewStmt
+			| CreateDomainStmt
 		;
 
 
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 806ccc63145..9b4a209378d 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -4464,6 +4464,32 @@ transformCreateSchemaStmtElements(ParseState *pstate, List *schemaElts,
 				elements = lappend(elements, element);
 				break;
 
+			case T_CreateDomainStmt:
+				{
+					char	   *domain_schema = NULL;
+
+					CreateDomainStmt *elp = castNode(CreateDomainStmt, element);
+
+					/*
+					 * The schema of the DOMAIN must match the schema being
+					 * created. If the domain name length exceeds 3, it will
+					 * fail in DeconstructQualifiedName.
+					 */
+					if (list_length(elp->domainname) == 2)
+						domain_schema = strVal(list_nth(elp->domainname, 0));
+					else if (list_length(elp->domainname) == 3)
+						domain_schema = strVal(list_nth(elp->domainname, 1));
+
+					if (domain_schema != NULL && strcmp(domain_schema, schemaName) != 0)
+						ereport(ERROR,
+								errcode(ERRCODE_INVALID_SCHEMA_DEFINITION),
+								errmsg("CREATE DOMAIN specifies a schema (%s) different from the one being created (%s)",
+									   domain_schema, schemaName));
+
+					elements = lappend(elements, element);
+				}
+				break;
+
 			default:
 				elog(ERROR, "unrecognized node type: %d",
 					 (int) nodeTag(element));
diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c
index 6484c6a3dd4..f62a4b28380 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -2201,7 +2201,7 @@ match_previous_words(int pattern_id,
 	{
 		/* only some object types can be created as part of CREATE SCHEMA */
 		if (HeadMatches("CREATE", "SCHEMA"))
-			COMPLETE_WITH("TABLE", "VIEW", "INDEX", "SEQUENCE", "TRIGGER",
+			COMPLETE_WITH("TABLE", "VIEW", "INDEX", "SEQUENCE", "TRIGGER", "DOMAIN",
 			/* for INDEX and TABLE/SEQUENCE, respectively */
 						  "UNIQUE", "UNLOGGED");
 		else
@@ -3492,15 +3492,15 @@ match_previous_words(int pattern_id,
 	else if (Matches("CREATE", "DATABASE", MatchAny, "STRATEGY"))
 		COMPLETE_WITH("WAL_LOG", "FILE_COPY");
 
-	/* CREATE DOMAIN */
-	else if (Matches("CREATE", "DOMAIN", MatchAny))
+	/* CREATE DOMAIN --- is allowed inside CREATE SCHEMA, so use TailMatches */
+	else if (TailMatches("CREATE", "DOMAIN", MatchAny))
 		COMPLETE_WITH("AS");
-	else if (Matches("CREATE", "DOMAIN", MatchAny, "AS"))
+	else if (TailMatches("CREATE", "DOMAIN", MatchAny, "AS"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes);
-	else if (Matches("CREATE", "DOMAIN", MatchAny, "AS", MatchAny))
+	else if (TailMatches("CREATE", "DOMAIN", MatchAny, "AS", MatchAny))
 		COMPLETE_WITH("COLLATE", "DEFAULT", "CONSTRAINT",
 					  "NOT NULL", "NULL", "CHECK (");
-	else if (Matches("CREATE", "DOMAIN", MatchAny, "COLLATE"))
+	else if (TailMatches("CREATE", "DOMAIN", MatchAny, "COLLATE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_collations);
 
 	/* CREATE EXTENSION */
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..634900d8456 100644
--- a/src/test/modules/test_ddl_deparse/expected/create_schema.out
+++ b/src/test/modules/test_ddl_deparse/expected/create_schema.out
@@ -13,7 +13,14 @@ CREATE SCHEMA IF NOT EXISTS baz;
 NOTICE:  schema "baz" already exists, skipping
 CREATE SCHEMA element_test
   CREATE TABLE foo (id int)
-  CREATE VIEW bar AS SELECT * FROM foo;
+  CREATE VIEW bar AS SELECT * FROM foo
+  CREATE DOMAIN d1 AS INT;
 NOTICE:  DDL test: type simple, tag CREATE SCHEMA
 NOTICE:  DDL test: type simple, tag CREATE TABLE
 NOTICE:  DDL test: type simple, tag CREATE VIEW
+NOTICE:  DDL test: type simple, tag CREATE DOMAIN
+DROP SCHEMA element_test CASCADE;
+NOTICE:  drop cascades to 3 other objects
+DETAIL:  drop cascades to table element_test.foo
+drop cascades to view element_test.bar
+drop cascades to type element_test.d1
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..b822517b64d 100644
--- a/src/test/modules/test_ddl_deparse/sql/create_schema.sql
+++ b/src/test/modules/test_ddl_deparse/sql/create_schema.sql
@@ -14,4 +14,7 @@ CREATE SCHEMA IF NOT EXISTS baz;
 
 CREATE SCHEMA element_test
   CREATE TABLE foo (id int)
-  CREATE VIEW bar AS SELECT * FROM foo;
+  CREATE VIEW bar AS SELECT * FROM foo
+  CREATE DOMAIN d1 AS INT;
+
+DROP SCHEMA element_test CASCADE;
diff --git a/src/test/regress/expected/create_schema.out b/src/test/regress/expected/create_schema.out
index 38530c282a9..0533c29a311 100644
--- a/src/test/regress/expected/create_schema.out
+++ b/src/test/regress/expected/create_schema.out
@@ -131,5 +131,61 @@ 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;
+-- Cases where the schema creation with domain.
+--fail. cannot CREATE DOMAIN to other schema
+CREATE SCHEMA regress_schema_2 AUTHORIZATION CURRENT_ROLE
+  CREATE DOMAIN public.ss AS TEXT NOT NULL DEFAULT 'hello' CONSTRAINT nn CHECK (VALUE <> 'hello')
+  CREATE TABLE t(a ss);
+ERROR:  CREATE DOMAIN specifies a schema (public) different from the one being created (regress_schema_2)
+--fail. cannot CREATE DOMAIN to other schema
+CREATE SCHEMA regress_schema_2 AUTHORIZATION CURRENT_ROLE
+  CREATE DOMAIN postgres.public.ss AS TEXT NOT NULL CONSTRAINT nn CHECK (VALUE <> 'hello') DEFAULT 'hello'
+  CREATE TABLE t(a ss);
+ERROR:  CREATE DOMAIN specifies a schema (public) different from the one being created (regress_schema_2)
+--fail. improper qualified name
+CREATE SCHEMA regress_schema_2 CREATE DOMAIN ss.postgres.regress_schema_2.ss AS TEXT;
+ERROR:  improper qualified name (too many dotted names): ss.postgres.regress_schema_2.ss
+--fail. Execute subcommands in order; we do not implicitly reorder them.
+CREATE SCHEMA regress_schema_2 AUTHORIZATION CURRENT_ROLE
+  CREATE DOMAIN ss1 AS ss
+  CREATE DOMAIN ss AS TEXT;
+ERROR:  type "ss" does not exist
+LINE 2:   CREATE DOMAIN ss1 AS ss
+                               ^
+--ok, qualified schema name for domain should be same as the created schema.
+CREATE SCHEMA regress_schema_2 AUTHORIZATION CURRENT_ROLE
+  CREATE DOMAIN regress_schema_2.ss AS TEXT NOT NULL CONSTRAINT nn CHECK (VALUE <> 'hello') DEFAULT 'hello' COLLATE "C"
+  CREATE TABLE t(a regress_schema_2.ss);
+\dD regress_schema_2.*
+                                            List of domains
+      Schema      | Name | Type | Collation | Nullable |    Default    |             Check              
+------------------+------+------+-----------+----------+---------------+--------------------------------
+ regress_schema_2 | ss   | text | C         | not null | 'hello'::text | CHECK (VALUE <> 'hello'::text)
+(1 row)
+
+--ok, no qualified schema name for domain.
+CREATE SCHEMA regress_schema_3 AUTHORIZATION CURRENT_ROLE
+  CREATE DOMAIN ss AS TEXT CONSTRAINT nn CHECK (VALUE <> 'hello') NOT NULL DEFAULT 'hello'
+  CREATE DOMAIN ss1 AS ss
+  CREATE VIEW test AS SELECT 'hello'::ss AS test
+  CREATE TABLE t(a ss1);
+\dD regress_schema_3.*
+                                                    List of domains
+      Schema      | Name |        Type         | Collation | Nullable |    Default    |             Check              
+------------------+------+---------------------+-----------+----------+---------------+--------------------------------
+ regress_schema_3 | ss   | text                |           | not null | 'hello'::text | CHECK (VALUE <> 'hello'::text)
+ regress_schema_3 | ss1  | regress_schema_3.ss |           |          | 'hello'::text | 
+(2 rows)
+
+DROP SCHEMA regress_schema_2 CASCADE;
+NOTICE:  drop cascades to 2 other objects
+DETAIL:  drop cascades to type regress_schema_2.ss
+drop cascades to table regress_schema_2.t
+DROP SCHEMA regress_schema_3 CASCADE;
+NOTICE:  drop cascades to 4 other objects
+DETAIL:  drop cascades to type regress_schema_3.ss
+drop cascades to type regress_schema_3.ss1
+drop cascades to view regress_schema_3.test
+drop cascades to table regress_schema_3.t
 -- Clean up
 DROP ROLE regress_create_schema_role;
diff --git a/src/test/regress/sql/create_schema.sql b/src/test/regress/sql/create_schema.sql
index b3dc1cfd758..54a07054767 100644
--- a/src/test/regress/sql/create_schema.sql
+++ b/src/test/regress/sql/create_schema.sql
@@ -71,5 +71,41 @@ CREATE SCHEMA regress_schema_1 AUTHORIZATION CURRENT_ROLE
 DROP SCHEMA regress_schema_1 CASCADE;
 RESET ROLE;
 
+-- Cases where the schema creation with domain.
+--fail. cannot CREATE DOMAIN to other schema
+CREATE SCHEMA regress_schema_2 AUTHORIZATION CURRENT_ROLE
+  CREATE DOMAIN public.ss AS TEXT NOT NULL DEFAULT 'hello' CONSTRAINT nn CHECK (VALUE <> 'hello')
+  CREATE TABLE t(a ss);
+
+--fail. cannot CREATE DOMAIN to other schema
+CREATE SCHEMA regress_schema_2 AUTHORIZATION CURRENT_ROLE
+  CREATE DOMAIN postgres.public.ss AS TEXT NOT NULL CONSTRAINT nn CHECK (VALUE <> 'hello') DEFAULT 'hello'
+  CREATE TABLE t(a ss);
+
+--fail. improper qualified name
+CREATE SCHEMA regress_schema_2 CREATE DOMAIN ss.postgres.regress_schema_2.ss AS TEXT;
+
+--fail. Execute subcommands in order; we do not implicitly reorder them.
+CREATE SCHEMA regress_schema_2 AUTHORIZATION CURRENT_ROLE
+  CREATE DOMAIN ss1 AS ss
+  CREATE DOMAIN ss AS TEXT;
+
+--ok, qualified schema name for domain should be same as the created schema.
+CREATE SCHEMA regress_schema_2 AUTHORIZATION CURRENT_ROLE
+  CREATE DOMAIN regress_schema_2.ss AS TEXT NOT NULL CONSTRAINT nn CHECK (VALUE <> 'hello') DEFAULT 'hello' COLLATE "C"
+  CREATE TABLE t(a regress_schema_2.ss);
+\dD regress_schema_2.*
+
+--ok, no qualified schema name for domain.
+CREATE SCHEMA regress_schema_3 AUTHORIZATION CURRENT_ROLE
+  CREATE DOMAIN ss AS TEXT CONSTRAINT nn CHECK (VALUE <> 'hello') NOT NULL DEFAULT 'hello'
+  CREATE DOMAIN ss1 AS ss
+  CREATE VIEW test AS SELECT 'hello'::ss AS test
+  CREATE TABLE t(a ss1);
+\dD regress_schema_3.*
+
+DROP SCHEMA regress_schema_2 CASCADE;
+DROP SCHEMA regress_schema_3 CASCADE;
+
 -- Clean up
 DROP ROLE regress_create_schema_role;
-- 
2.34.1



  [text/x-patch] v10-0001-Don-t-try-to-re-order-the-subcommands-of-CREATE-SCHEMA.patch (26.5K, 5-v10-0001-Don-t-try-to-re-order-the-subcommands-of-CREATE-SCHEMA.patch)
  download | inline diff:
From c5ac517b14ef4fee2d67f0bf59d8eca1a35d9fab Mon Sep 17 00:00:00 2001
From: jian he <[email protected]>
Date: Mon, 9 Mar 2026 16:29:39 +0800
Subject: [PATCH v10 1/5] Don't try to re-order the subcommands of CREATE
 SCHEMA
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

transformCreateSchemaStmtElements has always believed that it is
supposed to re-order the subcommands of CREATE SCHEMA into a safe
execution order.  However, it is nowhere near being capable of doing
that correctly.  Nor is there reason to think that it ever will be,
or that that is a well-defined requirement, or that there's any basis
in the SQL standard for it.  Moreover, the problem will get worse as
we add more subcommand types.  Let's just drop the whole idea and
execute the commands in the order given, which seems like a much less
astonishment-prone definition anyway.

Along the way, pass down a ParseState so that we can provide an
error cursor for the "wrong schema name" error, and fix
transformCreateSchemaStmtElements so that it doesn't scribble
on the parsetree passed to it.

Note: This will cause compatibility issue,
for example:
CREATE SCHEMA regress_schema_2
  CREATE VIEW abcd_view AS SELECT a FROM abcd
  CREATE TABLE abcd (a int);

With the patch, it will throw an error, whereas on HEAD it won’t.
Author: Tom Lane <[email protected]>

Discussion: https://postgr.es/m/[email protected]
---
 doc/src/sgml/ref/create_schema.sgml         |  10 +-
 src/backend/commands/extension.c            |   7 +-
 src/backend/commands/schemacmds.c           |  15 ++-
 src/backend/parser/parse_utilcmd.c          | 136 ++++++++------------
 src/backend/tcop/utility.c                  |   3 +-
 src/include/commands/schemacmds.h           |   7 +-
 src/include/parser/parse_utilcmd.h          |   3 +-
 src/test/regress/expected/create_schema.out |  37 ++++++
 src/test/regress/expected/create_view.out   |   2 +
 src/test/regress/expected/event_trigger.out |   2 +-
 src/test/regress/expected/namespace.out     |   7 +-
 src/test/regress/sql/create_schema.sql      |   5 +
 src/test/regress/sql/namespace.sql          |   8 +-
 src/tools/pgindent/typedefs.list            |   1 -
 14 files changed, 125 insertions(+), 118 deletions(-)

diff --git a/doc/src/sgml/ref/create_schema.sgml b/doc/src/sgml/ref/create_schema.sgml
index ed69298ccc6..625793a6b67 100644
--- a/doc/src/sgml/ref/create_schema.sgml
+++ b/doc/src/sgml/ref/create_schema.sgml
@@ -193,12 +193,10 @@ CREATE VIEW hollywood.winners AS
   </para>
 
   <para>
-   The SQL standard specifies that the subcommands in <command>CREATE
-   SCHEMA</command> can appear in any order.  The present
-   <productname>PostgreSQL</productname> implementation does not
-   handle all cases of forward references in subcommands; it might
-   sometimes be necessary to reorder the subcommands in order to avoid
-   forward references.
+   <productname>PostgreSQL</productname> executes the subcommands
+   in <command>CREATE SCHEMA</command> in the order given.  Other
+   implementations may try to rearrange the subcommands into dependency
+   order, but that is hard if not impossible to do correctly.
   </para>
 
   <para>
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 963618a64c4..7eed07df69f 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1951,14 +1951,17 @@ CreateExtensionInternal(char *extensionName,
 
 		if (!OidIsValid(schemaOid))
 		{
+			ParseState *pstate = make_parsestate(NULL);
 			CreateSchemaStmt *csstmt = makeNode(CreateSchemaStmt);
 
+			pstate->p_sourcetext = "(generated CREATE SCHEMA command)";
+
 			csstmt->schemaname = schemaName;
 			csstmt->authrole = NULL;	/* will be created by current user */
 			csstmt->schemaElts = NIL;
 			csstmt->if_not_exists = false;
-			CreateSchemaCommand(csstmt, "(generated CREATE SCHEMA command)",
-								-1, -1);
+
+			CreateSchemaCommand(pstate, csstmt, -1, -1);
 
 			/*
 			 * CreateSchemaCommand includes CommandCounterIncrement, so new
diff --git a/src/backend/commands/schemacmds.c b/src/backend/commands/schemacmds.c
index 4c51e920626..d52093c3d9d 100644
--- a/src/backend/commands/schemacmds.c
+++ b/src/backend/commands/schemacmds.c
@@ -49,7 +49,7 @@ static void AlterSchemaOwner_internal(HeapTuple tup, Relation rel, Oid newOwnerI
  * a subquery.
  */
 Oid
-CreateSchemaCommand(CreateSchemaStmt *stmt, const char *queryString,
+CreateSchemaCommand(ParseState *pstate, CreateSchemaStmt *stmt,
 					int stmt_location, int stmt_len)
 {
 	const char *schemaName = stmt->schemaname;
@@ -189,12 +189,13 @@ CreateSchemaCommand(CreateSchemaStmt *stmt, const char *queryString,
 
 	/*
 	 * Examine the list of commands embedded in the CREATE SCHEMA command, and
-	 * reorganize them into a sequentially executable order with no forward
-	 * references.  Note that the result is still a list of raw parsetrees ---
-	 * we cannot, in general, run parse analysis on one statement until we
-	 * have actually executed the prior ones.
+	 * do preliminary transformations (mostly, verify that none are trying to
+	 * create objects outside the new schema).  Note that the result is still
+	 * a list of raw parsetrees --- we cannot, in general, run parse analysis
+	 * on one statement until we have actually executed the prior ones.
 	 */
-	parsetree_list = transformCreateSchemaStmtElements(stmt->schemaElts,
+	parsetree_list = transformCreateSchemaStmtElements(pstate,
+													   stmt->schemaElts,
 													   schemaName);
 
 	/*
@@ -219,7 +220,7 @@ CreateSchemaCommand(CreateSchemaStmt *stmt, const char *queryString,
 
 		/* do this step */
 		ProcessUtility(wrapper,
-					   queryString,
+					   pstate->p_sourcetext,
 					   false,
 					   PROCESS_UTILITY_SUBCOMMAND,
 					   NULL,
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index cc244c49e9e..806ccc63145 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -98,18 +98,6 @@ typedef struct
 	bool		ofType;			/* true if statement contains OF typename */
 } CreateStmtContext;
 
-/* State shared by transformCreateSchemaStmtElements and its subroutines */
-typedef struct
-{
-	const char *schemaname;		/* name of schema */
-	List	   *sequences;		/* CREATE SEQUENCE items */
-	List	   *tables;			/* CREATE TABLE items */
-	List	   *views;			/* CREATE VIEW items */
-	List	   *indexes;		/* CREATE INDEX items */
-	List	   *triggers;		/* CREATE TRIGGER items */
-	List	   *grants;			/* GRANT items */
-} CreateSchemaStmtContext;
-
 
 static void transformColumnDefinition(CreateStmtContext *cxt,
 									  ColumnDef *column);
@@ -136,7 +124,8 @@ static void transformCheckConstraints(CreateStmtContext *cxt,
 static void transformConstraintAttrs(CreateStmtContext *cxt,
 									 List *constraintList);
 static void transformColumnType(CreateStmtContext *cxt, ColumnDef *column);
-static void setSchemaName(const char *context_schema, char **stmt_schema_name);
+static void checkSchemaName(ParseState *pstate, const char *context_schema,
+							RangeVar *relation);
 static void transformPartitionCmd(CreateStmtContext *cxt, PartitionBoundSpec *bound);
 static List *transformPartitionRangeBounds(ParseState *pstate, List *blist,
 										   Relation parent);
@@ -4394,51 +4383,35 @@ transformColumnType(CreateStmtContext *cxt, ColumnDef *column)
  * transformCreateSchemaStmtElements -
  *	  analyzes the elements of a CREATE SCHEMA statement
  *
- * Split the schema element list from a CREATE SCHEMA statement into
- * individual commands and place them in the result list in an order
- * such that there are no forward references (e.g. GRANT to a table
- * created later in the list). Note that the logic we use for determining
- * forward references is presently quite incomplete.
+ * 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.
+ * 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.
  *
  * "schemaName" is the name of the schema that will be used for the creation
- * of the objects listed, that may be compiled from the schema name defined
+ * of the objects listed.  It may be obtained from the schema name defined
  * in the statement or a role specification.
  *
- * SQL also allows constraints to make forward references, so thumb through
- * the table columns and move forward references to a posterior alter-table
- * command.
- *
  * 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: this breaks the rules a little bit by modifying schema-name fields
- * within passed-in structs.  However, the transformation would be the same
- * if done over, so it should be all right to scribble on the input to this
- * extent.
  */
 List *
-transformCreateSchemaStmtElements(List *schemaElts, const char *schemaName)
+transformCreateSchemaStmtElements(ParseState *pstate, List *schemaElts,
+								  const char *schemaName)
 {
-	CreateSchemaStmtContext cxt;
-	List	   *result;
-	ListCell   *elements;
-
-	cxt.schemaname = schemaName;
-	cxt.sequences = NIL;
-	cxt.tables = NIL;
-	cxt.views = NIL;
-	cxt.indexes = NIL;
-	cxt.triggers = NIL;
-	cxt.grants = NIL;
+	List	   *elements = NIL;
+	ListCell   *lc;
 
 	/*
-	 * Run through each schema element in the schema element list. Separate
-	 * statements by type, and do preliminary analysis.
+	 * Run through each schema element in the schema element list.  Check
+	 * target schema names, and collect the list of actions to be done.
 	 */
-	foreach(elements, schemaElts)
+	foreach(lc, schemaElts)
 	{
-		Node	   *element = lfirst(elements);
+		Node	   *element = lfirst(lc);
 
 		switch (nodeTag(element))
 		{
@@ -4446,8 +4419,8 @@ transformCreateSchemaStmtElements(List *schemaElts, const char *schemaName)
 				{
 					CreateSeqStmt *elp = (CreateSeqStmt *) element;
 
-					setSchemaName(cxt.schemaname, &elp->sequence->schemaname);
-					cxt.sequences = lappend(cxt.sequences, element);
+					checkSchemaName(pstate, schemaName, elp->sequence);
+					elements = lappend(elements, element);
 				}
 				break;
 
@@ -4455,12 +4428,8 @@ transformCreateSchemaStmtElements(List *schemaElts, const char *schemaName)
 				{
 					CreateStmt *elp = (CreateStmt *) element;
 
-					setSchemaName(cxt.schemaname, &elp->relation->schemaname);
-
-					/*
-					 * XXX todo: deal with constraints
-					 */
-					cxt.tables = lappend(cxt.tables, element);
+					checkSchemaName(pstate, schemaName, elp->relation);
+					elements = lappend(elements, element);
 				}
 				break;
 
@@ -4468,12 +4437,8 @@ transformCreateSchemaStmtElements(List *schemaElts, const char *schemaName)
 				{
 					ViewStmt   *elp = (ViewStmt *) element;
 
-					setSchemaName(cxt.schemaname, &elp->view->schemaname);
-
-					/*
-					 * XXX todo: deal with references between views
-					 */
-					cxt.views = lappend(cxt.views, element);
+					checkSchemaName(pstate, schemaName, elp->view);
+					elements = lappend(elements, element);
 				}
 				break;
 
@@ -4481,8 +4446,8 @@ transformCreateSchemaStmtElements(List *schemaElts, const char *schemaName)
 				{
 					IndexStmt  *elp = (IndexStmt *) element;
 
-					setSchemaName(cxt.schemaname, &elp->relation->schemaname);
-					cxt.indexes = lappend(cxt.indexes, element);
+					checkSchemaName(pstate, schemaName, elp->relation);
+					elements = lappend(elements, element);
 				}
 				break;
 
@@ -4490,13 +4455,13 @@ transformCreateSchemaStmtElements(List *schemaElts, const char *schemaName)
 				{
 					CreateTrigStmt *elp = (CreateTrigStmt *) element;
 
-					setSchemaName(cxt.schemaname, &elp->relation->schemaname);
-					cxt.triggers = lappend(cxt.triggers, element);
+					checkSchemaName(pstate, schemaName, elp->relation);
+					elements = lappend(elements, element);
 				}
 				break;
 
 			case T_GrantStmt:
-				cxt.grants = lappend(cxt.grants, element);
+				elements = lappend(elements, element);
 				break;
 
 			default:
@@ -4505,32 +4470,39 @@ transformCreateSchemaStmtElements(List *schemaElts, const char *schemaName)
 		}
 	}
 
-	result = NIL;
-	result = list_concat(result, cxt.sequences);
-	result = list_concat(result, cxt.tables);
-	result = list_concat(result, cxt.views);
-	result = list_concat(result, cxt.indexes);
-	result = list_concat(result, cxt.triggers);
-	result = list_concat(result, cxt.grants);
-
-	return result;
+	return elements;
 }
 
 /*
- * setSchemaName
- *		Set or check schema name in an element of a CREATE SCHEMA command
+ * checkSchemaName
+ *		Check schema name in an element of a CREATE SCHEMA command
+ *
+ * It's okay if the command doesn't specify a target schema name, because
+ * CreateSchemaCommand will set up the default creation schema to be the
+ * new schema.  But if a target schema name is given, it had better match.
+ * We also have to check that the command doesn't say CREATE TEMP, since
+ * that would likewise put the object into the wrong schema.
  */
 static void
-setSchemaName(const char *context_schema, char **stmt_schema_name)
+checkSchemaName(ParseState *pstate, const char *context_schema,
+				RangeVar *relation)
 {
-	if (*stmt_schema_name == NULL)
-		*stmt_schema_name = unconstify(char *, context_schema);
-	else if (strcmp(context_schema, *stmt_schema_name) != 0)
+	if (relation->schemaname != NULL &&
+		strcmp(context_schema, relation->schemaname) != 0)
 		ereport(ERROR,
-				(errcode(ERRCODE_INVALID_SCHEMA_DEFINITION),
-				 errmsg("CREATE specifies a schema (%s) "
-						"different from the one being created (%s)",
-						*stmt_schema_name, context_schema)));
+				errcode(ERRCODE_INVALID_SCHEMA_DEFINITION),
+				errmsg("CREATE specifies a schema (%s) different from the one being created (%s)",
+					   relation->schemaname, context_schema),
+				parser_errposition(pstate, relation->location));
+
+	if (relation->relpersistence == RELPERSISTENCE_TEMP)
+	{
+		/* spell this error the same as in RangeVarAdjustRelationPersistence */
+		ereport(ERROR,
+				errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+				errmsg("cannot create temporary relation in non-temporary schema"),
+				parser_errposition(pstate, relation->location));
+	}
 }
 
 /*
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index bf707f2d57f..8603bb2e58c 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1119,8 +1119,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				 * relation and attribute manipulation
 				 */
 			case T_CreateSchemaStmt:
-				CreateSchemaCommand((CreateSchemaStmt *) parsetree,
-									queryString,
+				CreateSchemaCommand(pstate, castNode(CreateSchemaStmt, parsetree),
 									pstmt->stmt_location,
 									pstmt->stmt_len);
 
diff --git a/src/include/commands/schemacmds.h b/src/include/commands/schemacmds.h
index d100e4691b3..559727be5de 100644
--- a/src/include/commands/schemacmds.h
+++ b/src/include/commands/schemacmds.h
@@ -16,12 +16,9 @@
 #define SCHEMACMDS_H
 
 #include "catalog/objectaddress.h"
-#include "nodes/parsenodes.h"
-
-extern Oid	CreateSchemaCommand(CreateSchemaStmt *stmt,
-								const char *queryString,
-								int stmt_location, int stmt_len);
+#include "parser/parse_node.h"
 
+extern Oid	CreateSchemaCommand(ParseState *pstate, CreateSchemaStmt *stmt, int stmt_location, int stmt_len);
 extern ObjectAddress RenameSchema(const char *oldname, const char *newname);
 extern ObjectAddress AlterSchemaOwner(const char *name, Oid newOwnerId);
 extern void AlterSchemaOwner_oid(Oid schemaoid, Oid newOwnerId);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 53f282f77ec..34c98e5122f 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -30,7 +30,8 @@ extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-extern List *transformCreateSchemaStmtElements(List *schemaElts,
+extern List *transformCreateSchemaStmtElements(ParseState *pstate,
+											   List *schemaElts,
 											   const char *schemaName);
 extern PartitionBoundSpec *transformPartitionBound(ParseState *pstate, Relation parent,
 												   PartitionBoundSpec *spec);
diff --git a/src/test/regress/expected/create_schema.out b/src/test/regress/expected/create_schema.out
index 93302a07efc..38530c282a9 100644
--- a/src/test/regress/expected/create_schema.out
+++ b/src/test/regress/expected/create_schema.out
@@ -9,55 +9,92 @@ CREATE ROLE regress_create_schema_role SUPERUSER;
 CREATE SCHEMA AUTHORIZATION regress_create_schema_role
   CREATE SEQUENCE schema_not_existing.seq;
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_create_schema_role)
+LINE 2:   CREATE SEQUENCE schema_not_existing.seq;
+                          ^
 CREATE SCHEMA AUTHORIZATION regress_create_schema_role
   CREATE TABLE schema_not_existing.tab (id int);
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_create_schema_role)
+LINE 2:   CREATE TABLE schema_not_existing.tab (id int);
+                       ^
 CREATE SCHEMA AUTHORIZATION regress_create_schema_role
   CREATE VIEW schema_not_existing.view AS SELECT 1;
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_create_schema_role)
+LINE 2:   CREATE VIEW schema_not_existing.view AS SELECT 1;
+                      ^
 CREATE SCHEMA AUTHORIZATION regress_create_schema_role
   CREATE INDEX ON schema_not_existing.tab (id);
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_create_schema_role)
+LINE 2:   CREATE INDEX ON schema_not_existing.tab (id);
+                          ^
 CREATE SCHEMA AUTHORIZATION regress_create_schema_role
   CREATE TRIGGER schema_trig BEFORE INSERT ON schema_not_existing.tab
   EXECUTE FUNCTION schema_trig.no_func();
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_create_schema_role)
+LINE 2:   CREATE TRIGGER schema_trig BEFORE INSERT ON schema_not_exi...
+                                                      ^
 -- Again, with a role specification and no schema names.
 SET ROLE regress_create_schema_role;
 CREATE SCHEMA AUTHORIZATION CURRENT_ROLE
   CREATE SEQUENCE schema_not_existing.seq;
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_create_schema_role)
+LINE 2:   CREATE SEQUENCE schema_not_existing.seq;
+                          ^
 CREATE SCHEMA AUTHORIZATION CURRENT_ROLE
   CREATE TABLE schema_not_existing.tab (id int);
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_create_schema_role)
+LINE 2:   CREATE TABLE schema_not_existing.tab (id int);
+                       ^
 CREATE SCHEMA AUTHORIZATION CURRENT_ROLE
   CREATE VIEW schema_not_existing.view AS SELECT 1;
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_create_schema_role)
+LINE 2:   CREATE VIEW schema_not_existing.view AS SELECT 1;
+                      ^
 CREATE SCHEMA AUTHORIZATION CURRENT_ROLE
   CREATE INDEX ON schema_not_existing.tab (id);
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_create_schema_role)
+LINE 2:   CREATE INDEX ON schema_not_existing.tab (id);
+                          ^
 CREATE SCHEMA AUTHORIZATION CURRENT_ROLE
   CREATE TRIGGER schema_trig BEFORE INSERT ON schema_not_existing.tab
   EXECUTE FUNCTION schema_trig.no_func();
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_create_schema_role)
+LINE 2:   CREATE TRIGGER schema_trig BEFORE INSERT ON schema_not_exi...
+                                                      ^
 -- Again, with a schema name and a role specification.
 CREATE SCHEMA regress_schema_1 AUTHORIZATION CURRENT_ROLE
   CREATE SEQUENCE schema_not_existing.seq;
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_schema_1)
+LINE 2:   CREATE SEQUENCE schema_not_existing.seq;
+                          ^
 CREATE SCHEMA regress_schema_1 AUTHORIZATION CURRENT_ROLE
   CREATE TABLE schema_not_existing.tab (id int);
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_schema_1)
+LINE 2:   CREATE TABLE schema_not_existing.tab (id int);
+                       ^
 CREATE SCHEMA regress_schema_1 AUTHORIZATION CURRENT_ROLE
   CREATE VIEW schema_not_existing.view AS SELECT 1;
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_schema_1)
+LINE 2:   CREATE VIEW schema_not_existing.view AS SELECT 1;
+                      ^
 CREATE SCHEMA regress_schema_1 AUTHORIZATION CURRENT_ROLE
   CREATE INDEX ON schema_not_existing.tab (id);
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_schema_1)
+LINE 2:   CREATE INDEX ON schema_not_existing.tab (id);
+                          ^
 CREATE SCHEMA regress_schema_1 AUTHORIZATION CURRENT_ROLE
   CREATE TRIGGER schema_trig BEFORE INSERT ON schema_not_existing.tab
   EXECUTE FUNCTION schema_trig.no_func();
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_schema_1)
+LINE 2:   CREATE TRIGGER schema_trig BEFORE INSERT ON schema_not_exi...
+                                                      ^
 RESET ROLE;
+--error. Forward reference is not allowed.
+CREATE SCHEMA regress_schema_2
+  CREATE VIEW abcd_view AS SELECT a FROM abcd
+  CREATE TABLE abcd (a int);
+ERROR:  relation "abcd" does not exist
+LINE 2:   CREATE VIEW abcd_view AS SELECT a FROM abcd
+                                                 ^
 -- Cases where the schema creation succeeds.
 -- The schema created matches the role name.
 CREATE SCHEMA AUTHORIZATION regress_create_schema_role
diff --git a/src/test/regress/expected/create_view.out b/src/test/regress/expected/create_view.out
index bc4f79938b3..63cf4b4371d 100644
--- a/src/test/regress/expected/create_view.out
+++ b/src/test/regress/expected/create_view.out
@@ -128,6 +128,8 @@ ERROR:  cannot create temporary relation in non-temporary schema
 CREATE SCHEMA test_view_schema
     CREATE TEMP VIEW testview AS SELECT 1;
 ERROR:  cannot create temporary relation in non-temporary schema
+LINE 2:     CREATE TEMP VIEW testview AS SELECT 1;
+                             ^
 -- joins: if any of the join relations are temporary, the view
 -- should also be temporary
 -- should be non-temp
diff --git a/src/test/regress/expected/event_trigger.out b/src/test/regress/expected/event_trigger.out
index f897b079e67..4c32e1dcaf5 100644
--- a/src/test/regress/expected/event_trigger.out
+++ b/src/test/regress/expected/event_trigger.out
@@ -425,13 +425,13 @@ NOTICE:  END: command_tag=CREATE TABLE type=table identity=evttrig.one
 NOTICE:  END: command_tag=CREATE INDEX type=index identity=evttrig.one_pkey
 NOTICE:  END: command_tag=ALTER SEQUENCE type=sequence identity=evttrig.one_col_a_seq
 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=CREATE INDEX type=index identity=evttrig.one_idx
 -- 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/expected/namespace.out b/src/test/regress/expected/namespace.out
index dbbda72d395..d02f3fd67d7 100644
--- a/src/test/regress/expected/namespace.out
+++ b/src/test/regress/expected/namespace.out
@@ -10,13 +10,10 @@ SELECT pg_catalog.set_config('search_path', ' ', false);
 (1 row)
 
 CREATE SCHEMA test_ns_schema_1
+       CREATE TABLE abc (a serial, b int UNIQUE)
        CREATE UNIQUE INDEX abc_a_idx ON abc (a)
        CREATE VIEW abc_view AS
-              SELECT a+1 AS a, b+1 AS b FROM abc
-       CREATE TABLE abc (
-              a serial,
-              b int UNIQUE
-       );
+              SELECT a+1 AS a, b+1 AS b FROM abc;
 -- verify that the correct search_path restored on abort
 SET search_path to public;
 BEGIN;
diff --git a/src/test/regress/sql/create_schema.sql b/src/test/regress/sql/create_schema.sql
index 1b7064247a1..b3dc1cfd758 100644
--- a/src/test/regress/sql/create_schema.sql
+++ b/src/test/regress/sql/create_schema.sql
@@ -47,6 +47,11 @@ CREATE SCHEMA regress_schema_1 AUTHORIZATION CURRENT_ROLE
   EXECUTE FUNCTION schema_trig.no_func();
 RESET ROLE;
 
+--error. Forward reference is not allowed.
+CREATE SCHEMA regress_schema_2
+  CREATE VIEW abcd_view AS SELECT a FROM abcd
+  CREATE TABLE abcd (a int);
+
 -- Cases where the schema creation succeeds.
 -- The schema created matches the role name.
 CREATE SCHEMA AUTHORIZATION regress_create_schema_role
diff --git a/src/test/regress/sql/namespace.sql b/src/test/regress/sql/namespace.sql
index 306cdc2d8c6..9433eb3c15c 100644
--- a/src/test/regress/sql/namespace.sql
+++ b/src/test/regress/sql/namespace.sql
@@ -7,15 +7,11 @@
 SELECT pg_catalog.set_config('search_path', ' ', false);
 
 CREATE SCHEMA test_ns_schema_1
+       CREATE TABLE abc (a serial, b int UNIQUE)
        CREATE UNIQUE INDEX abc_a_idx ON abc (a)
 
        CREATE VIEW abc_view AS
-              SELECT a+1 AS a, b+1 AS b FROM abc
-
-       CREATE TABLE abc (
-              a serial,
-              b int UNIQUE
-       );
+              SELECT a+1 AS a, b+1 AS b FROM abc;
 
 -- verify that the correct search_path restored on abort
 SET search_path to public;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3250564d4ff..370c83be5d3 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -572,7 +572,6 @@ CreateRangeStmt
 CreateReplicationSlotCmd
 CreateRoleStmt
 CreateSchemaStmt
-CreateSchemaStmtContext
 CreateSeqStmt
 CreateStatsStmt
 CreateStmt
-- 
2.34.1



  [text/x-patch] v10-0005-CREATE-SCHEMA-foreign-key-executed-at-the-end.patch (24.1K, 6-v10-0005-CREATE-SCHEMA-foreign-key-executed-at-the-end.patch)
  download | inline diff:
From 981fa017d6d71921586e09bf49f208e2667497f0 Mon Sep 17 00:00:00 2001
From: jian he <[email protected]>
Date: Tue, 10 Mar 2026 22:03:40 +0800
Subject: [PATCH v10 5/5] CREATE SCHEMA foreign key executed at the end

For CREATE SCHEMA, all subcommands are executed in the order they are specified.
However, to support foreign key cross referencing, we need transform FOREIGN KEY
clause into ALTER TABLE ... ADD FOREIGN KEY command and append them to the end
of the CREATE SCHEMA execution queue.

All CREATE TABLE commands are executed first, so subsequent ALTER TABLE ... ADD
FOREIGN KEY statements will not encounter any conflicts.

Discussion: https://postgr.es/m/[email protected]
Commitfest: https://commitfest.postgresql.org/patch/5985
---
 src/backend/commands/schemacmds.c             | 130 ++++++++++++++++++
 src/backend/parser/parse_utilcmd.c            |  36 +++--
 src/include/parser/parse_utilcmd.h            |   1 +
 .../expected/create_schema.out                |  25 ++++
 .../test_ddl_deparse/sql/create_schema.sql    |  11 ++
 src/test/regress/expected/create_schema.out   |  83 +++++++++++
 src/test/regress/expected/event_trigger.out   |   2 +-
 src/test/regress/sql/create_schema.sql        |  36 +++++
 8 files changed, 304 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/schemacmds.c b/src/backend/commands/schemacmds.c
index d52093c3d9d..621f3806772 100644
--- a/src/backend/commands/schemacmds.c
+++ b/src/backend/commands/schemacmds.c
@@ -198,6 +198,136 @@ CreateSchemaCommand(ParseState *pstate, CreateSchemaStmt *stmt,
 													   stmt->schemaElts,
 													   schemaName);
 
+	/*
+	 * Reorder the list of commands embedded in the CREATE SCHEMA statement.
+	 * We only do this for foreign keys. Foreign key constraints are
+	 * transformed into ALTER TABLE ... ADD FOREIGN KEY commands and moved to
+	 * the end of the CREATE SCHEMA to-do list (parsetree_list).
+	 *
+	 * At the same time, the original foreign key Constraint node must removed
+	 * from CreateStmt, since they have already been converted into
+	 * AlterTableCmds. No need to do parse analysis for the original foreign
+	 * key constraint nodes: because foreign key constraint only allow plain
+	 * column references.
+	 *
+	 * See also transformFKConstraints and transformConstraintAttrs.
+	 */
+	foreach(parsetree_item, parsetree_list)
+	{
+		ListCell   *elements;
+		CreateStmt *csstmt;
+
+		Node	   *stmt = (Node *) lfirst(parsetree_item);
+
+		if (!IsA(stmt, CreateStmt))
+			continue;
+
+		csstmt = castNode(CreateStmt, stmt);
+
+		foreach(elements, csstmt->tableElts)
+		{
+			AlterTableStmt *alterstmt;
+			AlterTableCmd *altercmd;
+
+			Node	   *element = lfirst(elements);
+
+			if (IsA(element, Constraint))
+			{
+				Constraint *constr = castNode(Constraint, element);
+
+				if (constr->contype != CONSTR_FOREIGN)
+					continue;
+
+				alterstmt = makeNode(AlterTableStmt);
+				alterstmt->relation = copyObject(csstmt->relation);
+				alterstmt->cmds = NIL;
+				alterstmt->objtype = OBJECT_TABLE;
+
+				altercmd = makeNode(AlterTableCmd);
+				altercmd->subtype = AT_AddConstraint;
+				altercmd->name = NULL;
+				altercmd->def = (Node *) copyObject(constr);
+
+				alterstmt->cmds = lappend(alterstmt->cmds, altercmd);
+
+				csstmt->tableElts = foreach_delete_current(csstmt->tableElts,
+														   elements);
+
+				parsetree_list = lappend(parsetree_list, alterstmt);
+			}
+			else if (IsA(element, ColumnDef))
+			{
+				ColumnDef  *entry = castNode(ColumnDef, element);
+
+				transformConstraintAttrs(pstate, entry->constraints);
+
+				for (int constrpos = 0; constrpos < list_length(entry->constraints); constrpos++)
+				{
+					Constraint *colconstr = list_nth_node(Constraint,
+														  entry->constraints,
+														  constrpos);
+
+					if (colconstr->contype != CONSTR_FOREIGN)
+						continue;
+
+					alterstmt = makeNode(AlterTableStmt);
+					altercmd = makeNode(AlterTableCmd);
+
+					colconstr->fk_attrs = list_make1(makeString(entry->colname));
+
+					alterstmt->relation = copyObject(csstmt->relation);
+					alterstmt->cmds = NIL;
+					alterstmt->objtype = OBJECT_TABLE;
+
+					altercmd->subtype = AT_AddConstraint;
+					altercmd->name = NULL;
+					altercmd->def = (Node *) copyObject(colconstr);
+					alterstmt->cmds = lappend(alterstmt->cmds, altercmd);
+
+					parsetree_list = lappend(parsetree_list, alterstmt);
+
+					/*
+					 * Column constraints separate the Constraint node from
+					 * its attributes; a full column-level foreign key
+					 * constraint may be represented by multiple Constraint
+					 * nodes. After transformConstraintAttrs, the main foreign
+					 * key Constraint node already contains all required
+					 * information. Therefore, when the foreign key Constraint
+					 * node is removed, the associated attribute (e.g. NOT
+					 * ENFORCED) nodes (which are also Constraint nodes) must
+					 * be removed as well.
+					 */
+					for (int restpos = constrpos + 1; restpos < list_length(entry->constraints);)
+					{
+						Constraint *nextcolconstr = list_nth_node(Constraint,
+																  entry->constraints,
+																  restpos);
+
+						if (nextcolconstr->contype == CONSTR_ATTR_DEFERRABLE ||
+							nextcolconstr->contype == CONSTR_ATTR_NOT_DEFERRABLE ||
+							nextcolconstr->contype == CONSTR_ATTR_DEFERRED ||
+							nextcolconstr->contype == CONSTR_ATTR_IMMEDIATE ||
+							nextcolconstr->contype == CONSTR_ATTR_NOT_ENFORCED)
+						{
+							entry->constraints = list_delete_nth_cell(entry->constraints,
+																	  restpos);
+						}
+						else
+							break;
+					}
+
+					entry->constraints = list_delete_nth_cell(entry->constraints, constrpos);
+
+					/*
+					 * We have already removed one FK Constraint node, update
+					 * constrpos now.
+					 */
+					constrpos = constrpos - 1;
+				}
+			}
+		}
+	}
+
 	/*
 	 * Execute each command contained in the CREATE SCHEMA.  Since the grammar
 	 * allows only utility commands in CREATE SCHEMA, there is no need to pass
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index b6ee8342fde..8eadfb65523 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -122,8 +122,6 @@ static void transformFKConstraints(CreateStmtContext *cxt,
 								   bool isAddConstraint);
 static void transformCheckConstraints(CreateStmtContext *cxt,
 									  bool skipValidation);
-static void transformConstraintAttrs(CreateStmtContext *cxt,
-									 List *constraintList);
 static void transformColumnType(CreateStmtContext *cxt, ColumnDef *column);
 static void checkSchemaName(ParseState *pstate, const char *context_schema,
 							RangeVar *relation);
@@ -693,7 +691,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
@@ -4195,8 +4193,8 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
  * EXCLUSION, and PRIMARY KEY constraints, but someday they ought to be
  * supported for other constraint types.
  */
-static void
-transformConstraintAttrs(CreateStmtContext *cxt, List *constraintList)
+void
+transformConstraintAttrs(ParseState *pstate, List *constraintList)
 {
 	Constraint *lastprimarycon = NULL;
 	bool		saw_deferrability = false;
@@ -4225,12 +4223,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 +4238,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 +4251,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 +4259,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 +4277,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 +4285,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 +4302,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 +4319,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;
 
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 34c98e5122f..9afcd91c5db 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -20,6 +20,7 @@ typedef struct AttrMap AttrMap; /* avoid including attmap.h here */
 
 
 extern List *transformCreateStmt(CreateStmt *stmt, const char *queryString);
+extern void transformConstraintAttrs(ParseState *pstate, List *constraintList);
 extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   const char *queryString,
 											   List **beforeStmts,
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 92da27c813b..89b571fc4c9 100644
--- a/src/test/modules/test_ddl_deparse/expected/create_schema.out
+++ b/src/test/modules/test_ddl_deparse/expected/create_schema.out
@@ -39,3 +39,28 @@ drop cascades to type element_test.floatrange
 drop cascades to type element_test.ss
 drop cascades to type element_test.sss
 drop cascades to type element_test.rainbow
+CREATE SCHEMA regress_schema_1
+CREATE TABLE t4(
+    b INT,
+    a INT REFERENCES t5 DEFERRABLE INITIALLY DEFERRED NOT ENFORCED
+          REFERENCES t6 DEFERRABLE INITIALLY DEFERRED NOT ENFORCED,
+    CONSTRAINT fk FOREIGN KEY (a) REFERENCES t6 DEFERRABLE INITIALLY DEFERRED NOT ENFORCED)
+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 abe0637c415..411b01bb640 100644
--- a/src/test/modules/test_ddl_deparse/sql/create_schema.sql
+++ b/src/test/modules/test_ddl_deparse/sql/create_schema.sql
@@ -23,3 +23,14 @@ CREATE SCHEMA element_test
   CREATE TYPE rainbow AS ENUM ('red', 'orange');
 
 DROP SCHEMA element_test CASCADE;
+
+CREATE SCHEMA regress_schema_1
+CREATE TABLE t4(
+    b INT,
+    a INT REFERENCES t5 DEFERRABLE INITIALLY DEFERRED NOT ENFORCED
+          REFERENCES t6 DEFERRABLE INITIALLY DEFERRED NOT ENFORCED,
+    CONSTRAINT fk FOREIGN KEY (a) REFERENCES t6 DEFERRABLE INITIALLY DEFERRED NOT ENFORCED)
+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 a33cbd0c0d9..6df41f9a2f3 100644
--- a/src/test/regress/expected/create_schema.out
+++ b/src/test/regress/expected/create_schema.out
@@ -282,6 +282,73 @@ CREATE SCHEMA regress_schema_7
  regress_schema_7 | regress_schema_7.sss             | 
 (5 rows)
 
+CREATE SCHEMA regress_schema_8
+    CREATE TABLE regress_schema_8.t2 (
+        b int,
+        a int REFERENCES t1 DEFERRABLE INITIALLY DEFERRED NOT ENFORCED
+              REFERENCES t3 DEFERRABLE INITIALLY DEFERRED NOT ENFORCED,
+        CONSTRAINT fk FOREIGN KEY (a) REFERENCES t1 DEFERRABLE INITIALLY DEFERRED NOT ENFORCED)
+    CREATE TABLE regress_schema_8.t1 (a int PRIMARY KEY)
+    CREATE TABLE t3 (a int PRIMARY KEY)
+    CREATE TABLE t4(b int,
+                    a int REFERENCES t5 DEFERRABLE INITIALLY DEFERRED NOT ENFORCED
+                          REFERENCES t6 DEFERRABLE INITIALLY DEFERRED NOT ENFORCED,
+        CONSTRAINT fk FOREIGN KEY (a) REFERENCES t6 DEFERRABLE INITIALLY DEFERRED NOT ENFORCED)
+    CREATE TABLE t5 (a int, b int, PRIMARY KEY (a))
+    CREATE TABLE t6 (a int, b int, PRIMARY KEY (a));
+\d regress_schema_8.t2
+            Table "regress_schema_8.t2"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ b      | integer |           |          | 
+ a      | integer |           |          | 
+Foreign-key constraints:
+    "fk" FOREIGN KEY (a) REFERENCES regress_schema_8.t1(a) DEFERRABLE INITIALLY DEFERRED NOT ENFORCED
+    "t2_a_fkey" FOREIGN KEY (a) REFERENCES regress_schema_8.t1(a) DEFERRABLE INITIALLY DEFERRED NOT ENFORCED
+    "t2_a_fkey1" FOREIGN KEY (a) REFERENCES regress_schema_8.t3(a) DEFERRABLE INITIALLY DEFERRED NOT ENFORCED
+
+\d regress_schema_8.t4
+            Table "regress_schema_8.t4"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ b      | integer |           |          | 
+ a      | integer |           |          | 
+Foreign-key constraints:
+    "fk" FOREIGN KEY (a) REFERENCES regress_schema_8.t6(a) DEFERRABLE INITIALLY DEFERRED NOT ENFORCED
+    "t4_a_fkey" FOREIGN KEY (a) REFERENCES regress_schema_8.t5(a) DEFERRABLE INITIALLY DEFERRED NOT ENFORCED
+    "t4_a_fkey1" FOREIGN KEY (a) REFERENCES regress_schema_8.t6(a) DEFERRABLE INITIALLY DEFERRED NOT ENFORCED
+
+CREATE SCHEMA regress_schema_9
+    CREATE TABLE t2 (a int, b int,
+        CONSTRAINT fk FOREIGN KEY (a, b) REFERENCES t1 MATCH FULL NOT DEFERRABLE INITIALLY IMMEDIATE NOT ENFORCED,
+        CONSTRAINT fk1 FOREIGN KEY (a, b) REFERENCES t3 MATCH FULL DEFERRABLE INITIALLY IMMEDIATE NOT ENFORCED)
+    CREATE TABLE t1 (a int, b int, PRIMARY KEY (b, a))
+    CREATE TABLE t3 (a int, b int, PRIMARY KEY (b, a))
+    CREATE TABLE t4 (a int, b int,
+        CONSTRAINT fk FOREIGN KEY (b) REFERENCES t6 MATCH FULL NOT DEFERRABLE INITIALLY IMMEDIATE NOT ENFORCED,
+        CONSTRAINT fk1 FOREIGN KEY (a) REFERENCES t5 MATCH FULL DEFERRABLE INITIALLY IMMEDIATE NOT ENFORCED)
+    CREATE TABLE t5 (a int, b int, PRIMARY KEY (a))
+    CREATE TABLE t6 (a int, b int, PRIMARY KEY (b));
+\d regress_schema_9.t2
+            Table "regress_schema_9.t2"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ a      | integer |           |          | 
+ b      | integer |           |          | 
+Foreign-key constraints:
+    "fk" FOREIGN KEY (a, b) REFERENCES regress_schema_9.t1(b, a) MATCH FULL NOT ENFORCED
+    "fk1" FOREIGN KEY (a, b) REFERENCES regress_schema_9.t3(b, a) MATCH FULL DEFERRABLE NOT ENFORCED
+
+\d regress_schema_9.t4
+            Table "regress_schema_9.t4"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ a      | integer |           |          | 
+ b      | integer |           |          | 
+Foreign-key constraints:
+    "fk" FOREIGN KEY (b) REFERENCES regress_schema_9.t6(b) MATCH FULL NOT ENFORCED
+    "fk1" FOREIGN KEY (a) REFERENCES regress_schema_9.t5(a) MATCH FULL DEFERRABLE NOT ENFORCED
+
 DROP SCHEMA regress_schema_2 CASCADE;
 NOTICE:  drop cascades to 2 other objects
 DETAIL:  drop cascades to type regress_schema_2.ss
@@ -313,5 +380,21 @@ drop cascades to type regress_schema_7.ss
 drop cascades to type regress_schema_7.sss
 drop cascades to type regress_schema_7.rainbow
 drop cascades to table regress_schema_7.t
+DROP SCHEMA regress_schema_8 CASCADE;
+NOTICE:  drop cascades to 6 other objects
+DETAIL:  drop cascades to table regress_schema_8.t2
+drop cascades to table regress_schema_8.t1
+drop cascades to table regress_schema_8.t3
+drop cascades to table regress_schema_8.t4
+drop cascades to table regress_schema_8.t5
+drop cascades to table regress_schema_8.t6
+DROP SCHEMA regress_schema_9 CASCADE;
+NOTICE:  drop cascades to 6 other objects
+DETAIL:  drop cascades to table regress_schema_9.t2
+drop cascades to table regress_schema_9.t1
+drop cascades to table regress_schema_9.t3
+drop cascades to table regress_schema_9.t4
+drop cascades to table regress_schema_9.t5
+drop cascades to table regress_schema_9.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 75fd928eacb..547016f54cd 100644
--- a/src/test/regress/sql/create_schema.sql
+++ b/src/test/regress/sql/create_schema.sql
@@ -168,12 +168,48 @@ CREATE SCHEMA regress_schema_7
   CREATE TABLE t(a floatrange, b ss, c rainbow);
 \dT regress_schema_7.*
 
+CREATE SCHEMA regress_schema_8
+    CREATE TABLE regress_schema_8.t2 (
+        b int,
+        a int REFERENCES t1 DEFERRABLE INITIALLY DEFERRED NOT ENFORCED
+              REFERENCES t3 DEFERRABLE INITIALLY DEFERRED NOT ENFORCED,
+        CONSTRAINT fk FOREIGN KEY (a) REFERENCES t1 DEFERRABLE INITIALLY DEFERRED NOT ENFORCED)
+    CREATE TABLE regress_schema_8.t1 (a int PRIMARY KEY)
+    CREATE TABLE t3 (a int PRIMARY KEY)
+    CREATE TABLE t4(b int,
+                    a int REFERENCES t5 DEFERRABLE INITIALLY DEFERRED NOT ENFORCED
+                          REFERENCES t6 DEFERRABLE INITIALLY DEFERRED NOT ENFORCED,
+        CONSTRAINT fk FOREIGN KEY (a) REFERENCES t6 DEFERRABLE INITIALLY DEFERRED NOT ENFORCED)
+    CREATE TABLE t5 (a int, b int, PRIMARY KEY (a))
+    CREATE TABLE t6 (a int, b int, PRIMARY KEY (a));
+
+\d regress_schema_8.t2
+\d regress_schema_8.t4
+
+CREATE SCHEMA regress_schema_9
+    CREATE TABLE t2 (a int, b int,
+        CONSTRAINT fk FOREIGN KEY (a, b) REFERENCES t1 MATCH FULL NOT DEFERRABLE INITIALLY IMMEDIATE NOT ENFORCED,
+        CONSTRAINT fk1 FOREIGN KEY (a, b) REFERENCES t3 MATCH FULL DEFERRABLE INITIALLY IMMEDIATE NOT ENFORCED)
+    CREATE TABLE t1 (a int, b int, PRIMARY KEY (b, a))
+    CREATE TABLE t3 (a int, b int, PRIMARY KEY (b, a))
+    CREATE TABLE t4 (a int, b int,
+        CONSTRAINT fk FOREIGN KEY (b) REFERENCES t6 MATCH FULL NOT DEFERRABLE INITIALLY IMMEDIATE NOT ENFORCED,
+        CONSTRAINT fk1 FOREIGN KEY (a) REFERENCES t5 MATCH FULL DEFERRABLE INITIALLY IMMEDIATE NOT ENFORCED)
+    CREATE TABLE t5 (a int, b int, PRIMARY KEY (a))
+    CREATE TABLE t6 (a int, b int, PRIMARY KEY (b));
+
+\d regress_schema_9.t2
+\d regress_schema_9.t4
+
+
 DROP SCHEMA regress_schema_2 CASCADE;
 DROP SCHEMA regress_schema_3 CASCADE;
 DROP SCHEMA regress_schema_4 CASCADE;
 DROP SCHEMA regress_schema_5 CASCADE;
 DROP SCHEMA regress_schema_6 CASCADE;
 DROP SCHEMA regress_schema_7 CASCADE;
+DROP SCHEMA regress_schema_8 CASCADE;
+DROP SCHEMA regress_schema_9 CASCADE;
 
 -- Clean up
 DROP ROLE regress_create_schema_role;
-- 
2.34.1



^ permalink  raw  reply  [nested|flat] 32+ messages in thread

* Re: CREATE SCHEMA ... CREATE DOMAIN support
  2024-11-30 19:08 Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  2024-12-03 19:35 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Peter Eisentraut <[email protected]>
  2024-12-03 20:07   ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  2024-12-11 06:22     ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <[email protected]>
  2024-12-11 17:08       ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  2025-08-19 03:36         ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <[email protected]>
  2025-08-22 08:59           ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <[email protected]>
  2025-09-02 21:24             ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  2025-12-17 13:29               ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <[email protected]>
  2025-12-31 01:40                 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <[email protected]>
  2026-01-19 15:06                   ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <[email protected]>
  2026-03-10 14:11                     ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <[email protected]>
@ 2026-03-30 02:44                       ` jian he <[email protected]>
  0 siblings, 0 replies; 32+ messages in thread

From: jian he @ 2026-03-30 02:44 UTC (permalink / raw)
  To: Tom Lane <[email protected]>; +Cc: Kirill Reshke <[email protected]>; Peter Eisentraut <[email protected]>; PostgreSQL Hackers <[email protected]>

Hi.
Rebase only.



--
jian
https://www.enterprisedb.com/


Attachments:

  [text/x-patch] v11-0005-CREATE-SCHEMA-foreign-key-executed-at-the-end.patch (24.1K, 2-v11-0005-CREATE-SCHEMA-foreign-key-executed-at-the-end.patch)
  download | inline diff:
From 760b13f4b6d6a75939607ce42e769bb5761cd28d Mon Sep 17 00:00:00 2001
From: jian he <[email protected]>
Date: Tue, 10 Mar 2026 22:03:40 +0800
Subject: [PATCH v11 5/5] CREATE SCHEMA foreign key executed at the end

For CREATE SCHEMA, all subcommands are executed in the order they are specified.
However, to support foreign key cross referencing, we need transform FOREIGN KEY
clause into ALTER TABLE ... ADD FOREIGN KEY command and append them to the end
of the CREATE SCHEMA execution queue.

All CREATE TABLE commands are executed first, so subsequent ALTER TABLE ... ADD
FOREIGN KEY statements will not encounter any conflicts.

Discussion: https://postgr.es/m/[email protected]
Commitfest: https://commitfest.postgresql.org/patch/5985
---
 src/backend/commands/schemacmds.c             | 130 ++++++++++++++++++
 src/backend/parser/parse_utilcmd.c            |  36 +++--
 src/include/parser/parse_utilcmd.h            |   1 +
 .../expected/create_schema.out                |  25 ++++
 .../test_ddl_deparse/sql/create_schema.sql    |  11 ++
 src/test/regress/expected/create_schema.out   |  83 +++++++++++
 src/test/regress/expected/event_trigger.out   |   2 +-
 src/test/regress/sql/create_schema.sql        |  36 +++++
 8 files changed, 304 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/schemacmds.c b/src/backend/commands/schemacmds.c
index d52093c3d9d..621f3806772 100644
--- a/src/backend/commands/schemacmds.c
+++ b/src/backend/commands/schemacmds.c
@@ -198,6 +198,136 @@ CreateSchemaCommand(ParseState *pstate, CreateSchemaStmt *stmt,
 													   stmt->schemaElts,
 													   schemaName);
 
+	/*
+	 * Reorder the list of commands embedded in the CREATE SCHEMA statement.
+	 * We only do this for foreign keys. Foreign key constraints are
+	 * transformed into ALTER TABLE ... ADD FOREIGN KEY commands and moved to
+	 * the end of the CREATE SCHEMA to-do list (parsetree_list).
+	 *
+	 * At the same time, the original foreign key Constraint node must removed
+	 * from CreateStmt, since they have already been converted into
+	 * AlterTableCmds. No need to do parse analysis for the original foreign
+	 * key constraint nodes: because foreign key constraint only allow plain
+	 * column references.
+	 *
+	 * See also transformFKConstraints and transformConstraintAttrs.
+	 */
+	foreach(parsetree_item, parsetree_list)
+	{
+		ListCell   *elements;
+		CreateStmt *csstmt;
+
+		Node	   *stmt = (Node *) lfirst(parsetree_item);
+
+		if (!IsA(stmt, CreateStmt))
+			continue;
+
+		csstmt = castNode(CreateStmt, stmt);
+
+		foreach(elements, csstmt->tableElts)
+		{
+			AlterTableStmt *alterstmt;
+			AlterTableCmd *altercmd;
+
+			Node	   *element = lfirst(elements);
+
+			if (IsA(element, Constraint))
+			{
+				Constraint *constr = castNode(Constraint, element);
+
+				if (constr->contype != CONSTR_FOREIGN)
+					continue;
+
+				alterstmt = makeNode(AlterTableStmt);
+				alterstmt->relation = copyObject(csstmt->relation);
+				alterstmt->cmds = NIL;
+				alterstmt->objtype = OBJECT_TABLE;
+
+				altercmd = makeNode(AlterTableCmd);
+				altercmd->subtype = AT_AddConstraint;
+				altercmd->name = NULL;
+				altercmd->def = (Node *) copyObject(constr);
+
+				alterstmt->cmds = lappend(alterstmt->cmds, altercmd);
+
+				csstmt->tableElts = foreach_delete_current(csstmt->tableElts,
+														   elements);
+
+				parsetree_list = lappend(parsetree_list, alterstmt);
+			}
+			else if (IsA(element, ColumnDef))
+			{
+				ColumnDef  *entry = castNode(ColumnDef, element);
+
+				transformConstraintAttrs(pstate, entry->constraints);
+
+				for (int constrpos = 0; constrpos < list_length(entry->constraints); constrpos++)
+				{
+					Constraint *colconstr = list_nth_node(Constraint,
+														  entry->constraints,
+														  constrpos);
+
+					if (colconstr->contype != CONSTR_FOREIGN)
+						continue;
+
+					alterstmt = makeNode(AlterTableStmt);
+					altercmd = makeNode(AlterTableCmd);
+
+					colconstr->fk_attrs = list_make1(makeString(entry->colname));
+
+					alterstmt->relation = copyObject(csstmt->relation);
+					alterstmt->cmds = NIL;
+					alterstmt->objtype = OBJECT_TABLE;
+
+					altercmd->subtype = AT_AddConstraint;
+					altercmd->name = NULL;
+					altercmd->def = (Node *) copyObject(colconstr);
+					alterstmt->cmds = lappend(alterstmt->cmds, altercmd);
+
+					parsetree_list = lappend(parsetree_list, alterstmt);
+
+					/*
+					 * Column constraints separate the Constraint node from
+					 * its attributes; a full column-level foreign key
+					 * constraint may be represented by multiple Constraint
+					 * nodes. After transformConstraintAttrs, the main foreign
+					 * key Constraint node already contains all required
+					 * information. Therefore, when the foreign key Constraint
+					 * node is removed, the associated attribute (e.g. NOT
+					 * ENFORCED) nodes (which are also Constraint nodes) must
+					 * be removed as well.
+					 */
+					for (int restpos = constrpos + 1; restpos < list_length(entry->constraints);)
+					{
+						Constraint *nextcolconstr = list_nth_node(Constraint,
+																  entry->constraints,
+																  restpos);
+
+						if (nextcolconstr->contype == CONSTR_ATTR_DEFERRABLE ||
+							nextcolconstr->contype == CONSTR_ATTR_NOT_DEFERRABLE ||
+							nextcolconstr->contype == CONSTR_ATTR_DEFERRED ||
+							nextcolconstr->contype == CONSTR_ATTR_IMMEDIATE ||
+							nextcolconstr->contype == CONSTR_ATTR_NOT_ENFORCED)
+						{
+							entry->constraints = list_delete_nth_cell(entry->constraints,
+																	  restpos);
+						}
+						else
+							break;
+					}
+
+					entry->constraints = list_delete_nth_cell(entry->constraints, constrpos);
+
+					/*
+					 * We have already removed one FK Constraint node, update
+					 * constrpos now.
+					 */
+					constrpos = constrpos - 1;
+				}
+			}
+		}
+	}
+
 	/*
 	 * Execute each command contained in the CREATE SCHEMA.  Since the grammar
 	 * allows only utility commands in CREATE SCHEMA, there is no need to pass
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 86fe8652093..a8f48544f73 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -123,8 +123,6 @@ static void transformFKConstraints(CreateStmtContext *cxt,
 								   bool isAddConstraint);
 static void transformCheckConstraints(CreateStmtContext *cxt,
 									  bool skipValidation);
-static void transformConstraintAttrs(CreateStmtContext *cxt,
-									 List *constraintList);
 static void transformColumnType(CreateStmtContext *cxt, ColumnDef *column);
 static void checkSchemaName(ParseState *pstate, const char *context_schema,
 							RangeVar *relation);
@@ -694,7 +692,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
@@ -4196,8 +4194,8 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
  * EXCLUSION, and PRIMARY KEY constraints, but someday they ought to be
  * supported for other constraint types.
  */
-static void
-transformConstraintAttrs(CreateStmtContext *cxt, List *constraintList)
+void
+transformConstraintAttrs(ParseState *pstate, List *constraintList)
 {
 	Constraint *lastprimarycon = NULL;
 	bool		saw_deferrability = false;
@@ -4226,12 +4224,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;
@@ -4241,12 +4239,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 &&
@@ -4254,7 +4252,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:
@@ -4262,12 +4260,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;
 
@@ -4280,7 +4278,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:
@@ -4288,12 +4286,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;
@@ -4305,12 +4303,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;
@@ -4322,12 +4320,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;
 
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 34c98e5122f..9afcd91c5db 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -20,6 +20,7 @@ typedef struct AttrMap AttrMap; /* avoid including attmap.h here */
 
 
 extern List *transformCreateStmt(CreateStmt *stmt, const char *queryString);
+extern void transformConstraintAttrs(ParseState *pstate, List *constraintList);
 extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   const char *queryString,
 											   List **beforeStmts,
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 92da27c813b..89b571fc4c9 100644
--- a/src/test/modules/test_ddl_deparse/expected/create_schema.out
+++ b/src/test/modules/test_ddl_deparse/expected/create_schema.out
@@ -39,3 +39,28 @@ drop cascades to type element_test.floatrange
 drop cascades to type element_test.ss
 drop cascades to type element_test.sss
 drop cascades to type element_test.rainbow
+CREATE SCHEMA regress_schema_1
+CREATE TABLE t4(
+    b INT,
+    a INT REFERENCES t5 DEFERRABLE INITIALLY DEFERRED NOT ENFORCED
+          REFERENCES t6 DEFERRABLE INITIALLY DEFERRED NOT ENFORCED,
+    CONSTRAINT fk FOREIGN KEY (a) REFERENCES t6 DEFERRABLE INITIALLY DEFERRED NOT ENFORCED)
+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 abe0637c415..411b01bb640 100644
--- a/src/test/modules/test_ddl_deparse/sql/create_schema.sql
+++ b/src/test/modules/test_ddl_deparse/sql/create_schema.sql
@@ -23,3 +23,14 @@ CREATE SCHEMA element_test
   CREATE TYPE rainbow AS ENUM ('red', 'orange');
 
 DROP SCHEMA element_test CASCADE;
+
+CREATE SCHEMA regress_schema_1
+CREATE TABLE t4(
+    b INT,
+    a INT REFERENCES t5 DEFERRABLE INITIALLY DEFERRED NOT ENFORCED
+          REFERENCES t6 DEFERRABLE INITIALLY DEFERRED NOT ENFORCED,
+    CONSTRAINT fk FOREIGN KEY (a) REFERENCES t6 DEFERRABLE INITIALLY DEFERRED NOT ENFORCED)
+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 a15bdc67687..8d8659a1db4 100644
--- a/src/test/regress/expected/create_schema.out
+++ b/src/test/regress/expected/create_schema.out
@@ -277,6 +277,73 @@ CREATE SCHEMA regress_schema_7
  regress_schema_7 | regress_schema_7.sss             | 
 (5 rows)
 
+CREATE SCHEMA regress_schema_8
+    CREATE TABLE regress_schema_8.t2 (
+        b int,
+        a int REFERENCES t1 DEFERRABLE INITIALLY DEFERRED NOT ENFORCED
+              REFERENCES t3 DEFERRABLE INITIALLY DEFERRED NOT ENFORCED,
+        CONSTRAINT fk FOREIGN KEY (a) REFERENCES t1 DEFERRABLE INITIALLY DEFERRED NOT ENFORCED)
+    CREATE TABLE regress_schema_8.t1 (a int PRIMARY KEY)
+    CREATE TABLE t3 (a int PRIMARY KEY)
+    CREATE TABLE t4(b int,
+                    a int REFERENCES t5 DEFERRABLE INITIALLY DEFERRED NOT ENFORCED
+                          REFERENCES t6 DEFERRABLE INITIALLY DEFERRED NOT ENFORCED,
+        CONSTRAINT fk FOREIGN KEY (a) REFERENCES t6 DEFERRABLE INITIALLY DEFERRED NOT ENFORCED)
+    CREATE TABLE t5 (a int, b int, PRIMARY KEY (a))
+    CREATE TABLE t6 (a int, b int, PRIMARY KEY (a));
+\d regress_schema_8.t2
+            Table "regress_schema_8.t2"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ b      | integer |           |          | 
+ a      | integer |           |          | 
+Foreign-key constraints:
+    "fk" FOREIGN KEY (a) REFERENCES regress_schema_8.t1(a) DEFERRABLE INITIALLY DEFERRED NOT ENFORCED
+    "t2_a_fkey" FOREIGN KEY (a) REFERENCES regress_schema_8.t1(a) DEFERRABLE INITIALLY DEFERRED NOT ENFORCED
+    "t2_a_fkey1" FOREIGN KEY (a) REFERENCES regress_schema_8.t3(a) DEFERRABLE INITIALLY DEFERRED NOT ENFORCED
+
+\d regress_schema_8.t4
+            Table "regress_schema_8.t4"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ b      | integer |           |          | 
+ a      | integer |           |          | 
+Foreign-key constraints:
+    "fk" FOREIGN KEY (a) REFERENCES regress_schema_8.t6(a) DEFERRABLE INITIALLY DEFERRED NOT ENFORCED
+    "t4_a_fkey" FOREIGN KEY (a) REFERENCES regress_schema_8.t5(a) DEFERRABLE INITIALLY DEFERRED NOT ENFORCED
+    "t4_a_fkey1" FOREIGN KEY (a) REFERENCES regress_schema_8.t6(a) DEFERRABLE INITIALLY DEFERRED NOT ENFORCED
+
+CREATE SCHEMA regress_schema_9
+    CREATE TABLE t2 (a int, b int,
+        CONSTRAINT fk FOREIGN KEY (a, b) REFERENCES t1 MATCH FULL NOT DEFERRABLE INITIALLY IMMEDIATE NOT ENFORCED,
+        CONSTRAINT fk1 FOREIGN KEY (a, b) REFERENCES t3 MATCH FULL DEFERRABLE INITIALLY IMMEDIATE NOT ENFORCED)
+    CREATE TABLE t1 (a int, b int, PRIMARY KEY (b, a))
+    CREATE TABLE t3 (a int, b int, PRIMARY KEY (b, a))
+    CREATE TABLE t4 (a int, b int,
+        CONSTRAINT fk FOREIGN KEY (b) REFERENCES t6 MATCH FULL NOT DEFERRABLE INITIALLY IMMEDIATE NOT ENFORCED,
+        CONSTRAINT fk1 FOREIGN KEY (a) REFERENCES t5 MATCH FULL DEFERRABLE INITIALLY IMMEDIATE NOT ENFORCED)
+    CREATE TABLE t5 (a int, b int, PRIMARY KEY (a))
+    CREATE TABLE t6 (a int, b int, PRIMARY KEY (b));
+\d regress_schema_9.t2
+            Table "regress_schema_9.t2"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ a      | integer |           |          | 
+ b      | integer |           |          | 
+Foreign-key constraints:
+    "fk" FOREIGN KEY (a, b) REFERENCES regress_schema_9.t1(b, a) MATCH FULL NOT ENFORCED
+    "fk1" FOREIGN KEY (a, b) REFERENCES regress_schema_9.t3(b, a) MATCH FULL DEFERRABLE NOT ENFORCED
+
+\d regress_schema_9.t4
+            Table "regress_schema_9.t4"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ a      | integer |           |          | 
+ b      | integer |           |          | 
+Foreign-key constraints:
+    "fk" FOREIGN KEY (b) REFERENCES regress_schema_9.t6(b) MATCH FULL NOT ENFORCED
+    "fk1" FOREIGN KEY (a) REFERENCES regress_schema_9.t5(a) MATCH FULL DEFERRABLE NOT ENFORCED
+
 DROP SCHEMA regress_schema_2 CASCADE;
 NOTICE:  drop cascades to 2 other objects
 DETAIL:  drop cascades to type regress_schema_2.ss
@@ -308,5 +375,21 @@ drop cascades to type regress_schema_7.ss
 drop cascades to type regress_schema_7.sss
 drop cascades to type regress_schema_7.rainbow
 drop cascades to table regress_schema_7.t
+DROP SCHEMA regress_schema_8 CASCADE;
+NOTICE:  drop cascades to 6 other objects
+DETAIL:  drop cascades to table regress_schema_8.t2
+drop cascades to table regress_schema_8.t1
+drop cascades to table regress_schema_8.t3
+drop cascades to table regress_schema_8.t4
+drop cascades to table regress_schema_8.t5
+drop cascades to table regress_schema_8.t6
+DROP SCHEMA regress_schema_9 CASCADE;
+NOTICE:  drop cascades to 6 other objects
+DETAIL:  drop cascades to table regress_schema_9.t2
+drop cascades to table regress_schema_9.t1
+drop cascades to table regress_schema_9.t3
+drop cascades to table regress_schema_9.t4
+drop cascades to table regress_schema_9.t5
+drop cascades to table regress_schema_9.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 b1c837d27ce..b5b71fb320e 100644
--- a/src/test/regress/sql/create_schema.sql
+++ b/src/test/regress/sql/create_schema.sql
@@ -161,12 +161,48 @@ CREATE SCHEMA regress_schema_7
   CREATE TABLE t(a floatrange, b ss, c rainbow);
 \dT regress_schema_7.*
 
+CREATE SCHEMA regress_schema_8
+    CREATE TABLE regress_schema_8.t2 (
+        b int,
+        a int REFERENCES t1 DEFERRABLE INITIALLY DEFERRED NOT ENFORCED
+              REFERENCES t3 DEFERRABLE INITIALLY DEFERRED NOT ENFORCED,
+        CONSTRAINT fk FOREIGN KEY (a) REFERENCES t1 DEFERRABLE INITIALLY DEFERRED NOT ENFORCED)
+    CREATE TABLE regress_schema_8.t1 (a int PRIMARY KEY)
+    CREATE TABLE t3 (a int PRIMARY KEY)
+    CREATE TABLE t4(b int,
+                    a int REFERENCES t5 DEFERRABLE INITIALLY DEFERRED NOT ENFORCED
+                          REFERENCES t6 DEFERRABLE INITIALLY DEFERRED NOT ENFORCED,
+        CONSTRAINT fk FOREIGN KEY (a) REFERENCES t6 DEFERRABLE INITIALLY DEFERRED NOT ENFORCED)
+    CREATE TABLE t5 (a int, b int, PRIMARY KEY (a))
+    CREATE TABLE t6 (a int, b int, PRIMARY KEY (a));
+
+\d regress_schema_8.t2
+\d regress_schema_8.t4
+
+CREATE SCHEMA regress_schema_9
+    CREATE TABLE t2 (a int, b int,
+        CONSTRAINT fk FOREIGN KEY (a, b) REFERENCES t1 MATCH FULL NOT DEFERRABLE INITIALLY IMMEDIATE NOT ENFORCED,
+        CONSTRAINT fk1 FOREIGN KEY (a, b) REFERENCES t3 MATCH FULL DEFERRABLE INITIALLY IMMEDIATE NOT ENFORCED)
+    CREATE TABLE t1 (a int, b int, PRIMARY KEY (b, a))
+    CREATE TABLE t3 (a int, b int, PRIMARY KEY (b, a))
+    CREATE TABLE t4 (a int, b int,
+        CONSTRAINT fk FOREIGN KEY (b) REFERENCES t6 MATCH FULL NOT DEFERRABLE INITIALLY IMMEDIATE NOT ENFORCED,
+        CONSTRAINT fk1 FOREIGN KEY (a) REFERENCES t5 MATCH FULL DEFERRABLE INITIALLY IMMEDIATE NOT ENFORCED)
+    CREATE TABLE t5 (a int, b int, PRIMARY KEY (a))
+    CREATE TABLE t6 (a int, b int, PRIMARY KEY (b));
+
+\d regress_schema_9.t2
+\d regress_schema_9.t4
+
+
 DROP SCHEMA regress_schema_2 CASCADE;
 DROP SCHEMA regress_schema_3 CASCADE;
 DROP SCHEMA regress_schema_4 CASCADE;
 DROP SCHEMA regress_schema_5 CASCADE;
 DROP SCHEMA regress_schema_6 CASCADE;
 DROP SCHEMA regress_schema_7 CASCADE;
+DROP SCHEMA regress_schema_8 CASCADE;
+DROP SCHEMA regress_schema_9 CASCADE;
 
 -- Clean up
 DROP ROLE regress_create_schema_role;
-- 
2.34.1



  [text/x-patch] v11-0002-CREATE-SCHEMA-CREATE-DOMAIN.patch (11.8K, 3-v11-0002-CREATE-SCHEMA-CREATE-DOMAIN.patch)
  download | inline diff:
From 2a3a621816ed96d5bad14e7d80403f72dc697b3b Mon Sep 17 00:00:00 2001
From: jian he <[email protected]>
Date: Mon, 30 Mar 2026 10:37:59 +0800
Subject: [PATCH v11 2/5] CREATE SCHEMA CREATE DOMAIN

The SQL standard allows domains to be specified in a CREATE SCHEMA statement.
This adds support for that capability.

For example:
    CREATE SCHEMA schema_name AUTHORIZATION CURRENT_ROLE
    create domain ss as text not null;

The domain will be created within the to be created schema.  The domain name can
be schema-qualified or database-qualified, however it's not allowed to let
domain create within a different schema.

Author: Kirill Reshke <[email protected]>
Author: Jian He <[email protected]>
Reviewed-by: Alvaro Herrera <[email protected]>
Reviewed-by: Tom Lane <[email protected]>

Discussion: https://postgr.es/m/CALdSSPh4jUSDsWu3K58hjO60wnTRR0DuO4CKRcwa8EVuOSfXxg@mail.gmail.com
Commitfest: https://commitfest.postgresql.org/patch/5985
---
 doc/src/sgml/ref/create_schema.sgml           |  4 +-
 src/backend/parser/gram.y                     |  1 +
 src/backend/parser/parse_utilcmd.c            | 19 +++++++
 src/bin/psql/tab-complete.in.c                | 12 ++---
 .../expected/create_schema.out                |  9 +++-
 .../test_ddl_deparse/sql/create_schema.sql    |  5 +-
 src/test/regress/expected/create_schema.out   | 51 +++++++++++++++++++
 src/test/regress/sql/create_schema.sql        | 31 +++++++++++
 8 files changed, 122 insertions(+), 10 deletions(-)

diff --git a/doc/src/sgml/ref/create_schema.sgml b/doc/src/sgml/ref/create_schema.sgml
index 625793a6b67..79186d2b936 100644
--- a/doc/src/sgml/ref/create_schema.sgml
+++ b/doc/src/sgml/ref/create_schema.sgml
@@ -100,8 +100,8 @@ CREATE SCHEMA IF NOT EXISTS AUTHORIZATION <replaceable class="parameter">role_sp
       <listitem>
        <para>
         An SQL statement defining an object to be created within the
-        schema. Currently, only <command>CREATE
-        TABLE</command>, <command>CREATE VIEW</command>, <command>CREATE
+        schema. Currently, only <command>CREATE DOMAIN</command>
+        <command>CREATE TABLE</command>, <command>CREATE VIEW</command>, <command>CREATE
         INDEX</command>, <command>CREATE SEQUENCE</command>, <command>CREATE
         TRIGGER</command> and <command>GRANT</command> are accepted as clauses
         within <command>CREATE SCHEMA</command>. Other kinds of objects may
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 0fea726cdd5..66fb186cf93 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -1678,6 +1678,7 @@ schema_stmt:
 			| CreateTrigStmt
 			| GrantStmt
 			| ViewStmt
+			| CreateDomainStmt
 		;
 
 
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 5df74db22ff..051a2d0b1d6 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -4465,6 +4465,25 @@ transformCreateSchemaStmtElements(ParseState *pstate, List *schemaElts,
 				elements = lappend(elements, element);
 				break;
 
+			case T_CreateDomainStmt:
+				{
+					char	   *domain_schema = NULL;
+					char	   *domain_name = NULL;
+
+					CreateDomainStmt *elp = castNode(CreateDomainStmt, element);
+
+					DeconstructQualifiedName(elp->domainname, &domain_schema, &domain_name);
+
+					if (domain_schema != NULL && strcmp(domain_schema, schemaName) != 0)
+						ereport(ERROR,
+								errcode(ERRCODE_INVALID_SCHEMA_DEFINITION),
+								errmsg("CREATE DOMAIN specifies a schema (%s) different from the one being created (%s)",
+									   domain_schema, schemaName));
+
+					elements = lappend(elements, element);
+				}
+				break;
+
 			default:
 				elog(ERROR, "unrecognized node type: %d",
 					 (int) nodeTag(element));
diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c
index 523d3f39fc5..accab281cdc 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -2210,7 +2210,7 @@ match_previous_words(int pattern_id,
 	{
 		/* only some object types can be created as part of CREATE SCHEMA */
 		if (HeadMatches("CREATE", "SCHEMA"))
-			COMPLETE_WITH("TABLE", "VIEW", "INDEX", "SEQUENCE", "TRIGGER",
+			COMPLETE_WITH("TABLE", "VIEW", "INDEX", "SEQUENCE", "TRIGGER", "DOMAIN",
 			/* for INDEX and TABLE/SEQUENCE, respectively */
 						  "UNIQUE", "UNLOGGED");
 		else
@@ -3530,15 +3530,15 @@ match_previous_words(int pattern_id,
 	else if (Matches("CREATE", "DATABASE", MatchAny, "STRATEGY"))
 		COMPLETE_WITH("WAL_LOG", "FILE_COPY");
 
-	/* CREATE DOMAIN */
-	else if (Matches("CREATE", "DOMAIN", MatchAny))
+	/* CREATE DOMAIN --- is allowed inside CREATE SCHEMA, so use TailMatches */
+	else if (TailMatches("CREATE", "DOMAIN", MatchAny))
 		COMPLETE_WITH("AS");
-	else if (Matches("CREATE", "DOMAIN", MatchAny, "AS"))
+	else if (TailMatches("CREATE", "DOMAIN", MatchAny, "AS"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes);
-	else if (Matches("CREATE", "DOMAIN", MatchAny, "AS", MatchAny))
+	else if (TailMatches("CREATE", "DOMAIN", MatchAny, "AS", MatchAny))
 		COMPLETE_WITH("COLLATE", "DEFAULT", "CONSTRAINT",
 					  "NOT NULL", "NULL", "CHECK (");
-	else if (Matches("CREATE", "DOMAIN", MatchAny, "COLLATE"))
+	else if (TailMatches("CREATE", "DOMAIN", MatchAny, "COLLATE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_collations);
 
 	/* CREATE EXTENSION */
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..634900d8456 100644
--- a/src/test/modules/test_ddl_deparse/expected/create_schema.out
+++ b/src/test/modules/test_ddl_deparse/expected/create_schema.out
@@ -13,7 +13,14 @@ CREATE SCHEMA IF NOT EXISTS baz;
 NOTICE:  schema "baz" already exists, skipping
 CREATE SCHEMA element_test
   CREATE TABLE foo (id int)
-  CREATE VIEW bar AS SELECT * FROM foo;
+  CREATE VIEW bar AS SELECT * FROM foo
+  CREATE DOMAIN d1 AS INT;
 NOTICE:  DDL test: type simple, tag CREATE SCHEMA
 NOTICE:  DDL test: type simple, tag CREATE TABLE
 NOTICE:  DDL test: type simple, tag CREATE VIEW
+NOTICE:  DDL test: type simple, tag CREATE DOMAIN
+DROP SCHEMA element_test CASCADE;
+NOTICE:  drop cascades to 3 other objects
+DETAIL:  drop cascades to table element_test.foo
+drop cascades to view element_test.bar
+drop cascades to type element_test.d1
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..b822517b64d 100644
--- a/src/test/modules/test_ddl_deparse/sql/create_schema.sql
+++ b/src/test/modules/test_ddl_deparse/sql/create_schema.sql
@@ -14,4 +14,7 @@ CREATE SCHEMA IF NOT EXISTS baz;
 
 CREATE SCHEMA element_test
   CREATE TABLE foo (id int)
-  CREATE VIEW bar AS SELECT * FROM foo;
+  CREATE VIEW bar AS SELECT * FROM foo
+  CREATE DOMAIN d1 AS INT;
+
+DROP SCHEMA element_test CASCADE;
diff --git a/src/test/regress/expected/create_schema.out b/src/test/regress/expected/create_schema.out
index 38530c282a9..353a111ba95 100644
--- a/src/test/regress/expected/create_schema.out
+++ b/src/test/regress/expected/create_schema.out
@@ -131,5 +131,56 @@ 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;
+-- Cases where the schema creation with domain
+-- fail, cannot CREATE DOMAIN to other schema
+CREATE SCHEMA regress_schema_2 AUTHORIZATION CURRENT_ROLE
+  CREATE DOMAIN public.ss AS TEXT NOT NULL DEFAULT 'hello' CONSTRAINT nn CHECK (VALUE <> 'hello')
+  CREATE TABLE t(a ss);
+ERROR:  CREATE DOMAIN specifies a schema (public) different from the one being created (regress_schema_2)
+-- fail. improper qualified name
+CREATE SCHEMA regress_schema_2 CREATE DOMAIN ss.postgres.regress_schema_2.ss AS TEXT;
+ERROR:  improper qualified name (too many dotted names): ss.postgres.regress_schema_2.ss
+-- fail, execute subcommands in order; we do not implicitly reorder them
+CREATE SCHEMA regress_schema_2 AUTHORIZATION CURRENT_ROLE
+  CREATE DOMAIN ss1 AS ss
+  CREATE DOMAIN ss AS TEXT;
+ERROR:  type "ss" does not exist
+LINE 2:   CREATE DOMAIN ss1 AS ss
+                               ^
+--ok, qualified schema name for domain should be same as the created schema
+CREATE SCHEMA regress_schema_2 AUTHORIZATION CURRENT_ROLE
+  CREATE DOMAIN regress_schema_2.ss AS TEXT NOT NULL CONSTRAINT nn CHECK (VALUE <> 'hello') DEFAULT 'hello' COLLATE "C"
+  CREATE TABLE t(a regress_schema_2.ss);
+\dD regress_schema_2.*
+                                            List of domains
+      Schema      | Name | Type | Collation | Nullable |    Default    |             Check              
+------------------+------+------+-----------+----------+---------------+--------------------------------
+ regress_schema_2 | ss   | text | C         | not null | 'hello'::text | CHECK (VALUE <> 'hello'::text)
+(1 row)
+
+-- ok, no qualified schema name for domain
+CREATE SCHEMA regress_schema_3 AUTHORIZATION CURRENT_ROLE
+  CREATE DOMAIN ss AS TEXT CONSTRAINT nn CHECK (VALUE <> 'hello') NOT NULL DEFAULT 'hello'
+  CREATE DOMAIN ss1 AS ss
+  CREATE VIEW test AS SELECT 'hello'::ss AS test
+  CREATE TABLE t(a ss1);
+\dD regress_schema_3.*
+                                                    List of domains
+      Schema      | Name |        Type         | Collation | Nullable |    Default    |             Check              
+------------------+------+---------------------+-----------+----------+---------------+--------------------------------
+ regress_schema_3 | ss   | text                |           | not null | 'hello'::text | CHECK (VALUE <> 'hello'::text)
+ regress_schema_3 | ss1  | regress_schema_3.ss |           |          | 'hello'::text | 
+(2 rows)
+
+DROP SCHEMA regress_schema_2 CASCADE;
+NOTICE:  drop cascades to 2 other objects
+DETAIL:  drop cascades to type regress_schema_2.ss
+drop cascades to table regress_schema_2.t
+DROP SCHEMA regress_schema_3 CASCADE;
+NOTICE:  drop cascades to 4 other objects
+DETAIL:  drop cascades to type regress_schema_3.ss
+drop cascades to type regress_schema_3.ss1
+drop cascades to view regress_schema_3.test
+drop cascades to table regress_schema_3.t
 -- Clean up
 DROP ROLE regress_create_schema_role;
diff --git a/src/test/regress/sql/create_schema.sql b/src/test/regress/sql/create_schema.sql
index b3dc1cfd758..ec47b90c673 100644
--- a/src/test/regress/sql/create_schema.sql
+++ b/src/test/regress/sql/create_schema.sql
@@ -71,5 +71,36 @@ CREATE SCHEMA regress_schema_1 AUTHORIZATION CURRENT_ROLE
 DROP SCHEMA regress_schema_1 CASCADE;
 RESET ROLE;
 
+-- Cases where the schema creation with domain
+-- fail, cannot CREATE DOMAIN to other schema
+CREATE SCHEMA regress_schema_2 AUTHORIZATION CURRENT_ROLE
+  CREATE DOMAIN public.ss AS TEXT NOT NULL DEFAULT 'hello' CONSTRAINT nn CHECK (VALUE <> 'hello')
+  CREATE TABLE t(a ss);
+
+-- fail. improper qualified name
+CREATE SCHEMA regress_schema_2 CREATE DOMAIN ss.postgres.regress_schema_2.ss AS TEXT;
+
+-- fail, execute subcommands in order; we do not implicitly reorder them
+CREATE SCHEMA regress_schema_2 AUTHORIZATION CURRENT_ROLE
+  CREATE DOMAIN ss1 AS ss
+  CREATE DOMAIN ss AS TEXT;
+
+--ok, qualified schema name for domain should be same as the created schema
+CREATE SCHEMA regress_schema_2 AUTHORIZATION CURRENT_ROLE
+  CREATE DOMAIN regress_schema_2.ss AS TEXT NOT NULL CONSTRAINT nn CHECK (VALUE <> 'hello') DEFAULT 'hello' COLLATE "C"
+  CREATE TABLE t(a regress_schema_2.ss);
+\dD regress_schema_2.*
+
+-- ok, no qualified schema name for domain
+CREATE SCHEMA regress_schema_3 AUTHORIZATION CURRENT_ROLE
+  CREATE DOMAIN ss AS TEXT CONSTRAINT nn CHECK (VALUE <> 'hello') NOT NULL DEFAULT 'hello'
+  CREATE DOMAIN ss1 AS ss
+  CREATE VIEW test AS SELECT 'hello'::ss AS test
+  CREATE TABLE t(a ss1);
+\dD regress_schema_3.*
+
+DROP SCHEMA regress_schema_2 CASCADE;
+DROP SCHEMA regress_schema_3 CASCADE;
+
 -- Clean up
 DROP ROLE regress_create_schema_role;
-- 
2.34.1



  [text/x-patch] v11-0001-Don-t-try-to-re-order-the-subcommands-of-CREATE-SCHEMA.patch (26.5K, 4-v11-0001-Don-t-try-to-re-order-the-subcommands-of-CREATE-SCHEMA.patch)
  download | inline diff:
From 04342a12b20c17c3374704129a3e5c35c02b5823 Mon Sep 17 00:00:00 2001
From: jian he <[email protected]>
Date: Mon, 9 Mar 2026 16:29:39 +0800
Subject: [PATCH v11 1/5] Don't try to re-order the subcommands of CREATE
 SCHEMA
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

transformCreateSchemaStmtElements has always believed that it is
supposed to re-order the subcommands of CREATE SCHEMA into a safe
execution order.  However, it is nowhere near being capable of doing
that correctly.  Nor is there reason to think that it ever will be,
or that that is a well-defined requirement, or that there's any basis
in the SQL standard for it.  Moreover, the problem will get worse as
we add more subcommand types.  Let's just drop the whole idea and
execute the commands in the order given, which seems like a much less
astonishment-prone definition anyway.

Along the way, pass down a ParseState so that we can provide an
error cursor for the "wrong schema name" error, and fix
transformCreateSchemaStmtElements so that it doesn't scribble
on the parsetree passed to it.

Note: This will cause compatibility issue,
for example:
CREATE SCHEMA regress_schema_2
  CREATE VIEW abcd_view AS SELECT a FROM abcd
  CREATE TABLE abcd (a int);

With the patch, it will throw an error, whereas on HEAD it won’t.
Author: Tom Lane <[email protected]>

Discussion: https://postgr.es/m/[email protected]
---
 doc/src/sgml/ref/create_schema.sgml         |  10 +-
 src/backend/commands/extension.c            |   7 +-
 src/backend/commands/schemacmds.c           |  15 ++-
 src/backend/parser/parse_utilcmd.c          | 136 ++++++++------------
 src/backend/tcop/utility.c                  |   3 +-
 src/include/commands/schemacmds.h           |   7 +-
 src/include/parser/parse_utilcmd.h          |   3 +-
 src/test/regress/expected/create_schema.out |  37 ++++++
 src/test/regress/expected/create_view.out   |   2 +
 src/test/regress/expected/event_trigger.out |   2 +-
 src/test/regress/expected/namespace.out     |   7 +-
 src/test/regress/sql/create_schema.sql      |   5 +
 src/test/regress/sql/namespace.sql          |   8 +-
 src/tools/pgindent/typedefs.list            |   1 -
 14 files changed, 125 insertions(+), 118 deletions(-)

diff --git a/doc/src/sgml/ref/create_schema.sgml b/doc/src/sgml/ref/create_schema.sgml
index ed69298ccc6..625793a6b67 100644
--- a/doc/src/sgml/ref/create_schema.sgml
+++ b/doc/src/sgml/ref/create_schema.sgml
@@ -193,12 +193,10 @@ CREATE VIEW hollywood.winners AS
   </para>
 
   <para>
-   The SQL standard specifies that the subcommands in <command>CREATE
-   SCHEMA</command> can appear in any order.  The present
-   <productname>PostgreSQL</productname> implementation does not
-   handle all cases of forward references in subcommands; it might
-   sometimes be necessary to reorder the subcommands in order to avoid
-   forward references.
+   <productname>PostgreSQL</productname> executes the subcommands
+   in <command>CREATE SCHEMA</command> in the order given.  Other
+   implementations may try to rearrange the subcommands into dependency
+   order, but that is hard if not impossible to do correctly.
   </para>
 
   <para>
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index b98801d08f2..a330b5fd6ce 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1952,14 +1952,17 @@ CreateExtensionInternal(char *extensionName,
 
 		if (!OidIsValid(schemaOid))
 		{
+			ParseState *pstate = make_parsestate(NULL);
 			CreateSchemaStmt *csstmt = makeNode(CreateSchemaStmt);
 
+			pstate->p_sourcetext = "(generated CREATE SCHEMA command)";
+
 			csstmt->schemaname = schemaName;
 			csstmt->authrole = NULL;	/* will be created by current user */
 			csstmt->schemaElts = NIL;
 			csstmt->if_not_exists = false;
-			CreateSchemaCommand(csstmt, "(generated CREATE SCHEMA command)",
-								-1, -1);
+
+			CreateSchemaCommand(pstate, csstmt, -1, -1);
 
 			/*
 			 * CreateSchemaCommand includes CommandCounterIncrement, so new
diff --git a/src/backend/commands/schemacmds.c b/src/backend/commands/schemacmds.c
index 4c51e920626..d52093c3d9d 100644
--- a/src/backend/commands/schemacmds.c
+++ b/src/backend/commands/schemacmds.c
@@ -49,7 +49,7 @@ static void AlterSchemaOwner_internal(HeapTuple tup, Relation rel, Oid newOwnerI
  * a subquery.
  */
 Oid
-CreateSchemaCommand(CreateSchemaStmt *stmt, const char *queryString,
+CreateSchemaCommand(ParseState *pstate, CreateSchemaStmt *stmt,
 					int stmt_location, int stmt_len)
 {
 	const char *schemaName = stmt->schemaname;
@@ -189,12 +189,13 @@ CreateSchemaCommand(CreateSchemaStmt *stmt, const char *queryString,
 
 	/*
 	 * Examine the list of commands embedded in the CREATE SCHEMA command, and
-	 * reorganize them into a sequentially executable order with no forward
-	 * references.  Note that the result is still a list of raw parsetrees ---
-	 * we cannot, in general, run parse analysis on one statement until we
-	 * have actually executed the prior ones.
+	 * do preliminary transformations (mostly, verify that none are trying to
+	 * create objects outside the new schema).  Note that the result is still
+	 * a list of raw parsetrees --- we cannot, in general, run parse analysis
+	 * on one statement until we have actually executed the prior ones.
 	 */
-	parsetree_list = transformCreateSchemaStmtElements(stmt->schemaElts,
+	parsetree_list = transformCreateSchemaStmtElements(pstate,
+													   stmt->schemaElts,
 													   schemaName);
 
 	/*
@@ -219,7 +220,7 @@ CreateSchemaCommand(CreateSchemaStmt *stmt, const char *queryString,
 
 		/* do this step */
 		ProcessUtility(wrapper,
-					   queryString,
+					   pstate->p_sourcetext,
 					   false,
 					   PROCESS_UTILITY_SUBCOMMAND,
 					   NULL,
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 9a918e14aa7..5df74db22ff 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -99,18 +99,6 @@ typedef struct
 	bool		ofType;			/* true if statement contains OF typename */
 } CreateStmtContext;
 
-/* State shared by transformCreateSchemaStmtElements and its subroutines */
-typedef struct
-{
-	const char *schemaname;		/* name of schema */
-	List	   *sequences;		/* CREATE SEQUENCE items */
-	List	   *tables;			/* CREATE TABLE items */
-	List	   *views;			/* CREATE VIEW items */
-	List	   *indexes;		/* CREATE INDEX items */
-	List	   *triggers;		/* CREATE TRIGGER items */
-	List	   *grants;			/* GRANT items */
-} CreateSchemaStmtContext;
-
 
 static void transformColumnDefinition(CreateStmtContext *cxt,
 									  ColumnDef *column);
@@ -137,7 +125,8 @@ static void transformCheckConstraints(CreateStmtContext *cxt,
 static void transformConstraintAttrs(CreateStmtContext *cxt,
 									 List *constraintList);
 static void transformColumnType(CreateStmtContext *cxt, ColumnDef *column);
-static void setSchemaName(const char *context_schema, char **stmt_schema_name);
+static void checkSchemaName(ParseState *pstate, const char *context_schema,
+							RangeVar *relation);
 static void transformPartitionCmd(CreateStmtContext *cxt, PartitionBoundSpec *bound);
 static List *transformPartitionRangeBounds(ParseState *pstate, List *blist,
 										   Relation parent);
@@ -4395,51 +4384,35 @@ transformColumnType(CreateStmtContext *cxt, ColumnDef *column)
  * transformCreateSchemaStmtElements -
  *	  analyzes the elements of a CREATE SCHEMA statement
  *
- * Split the schema element list from a CREATE SCHEMA statement into
- * individual commands and place them in the result list in an order
- * such that there are no forward references (e.g. GRANT to a table
- * created later in the list). Note that the logic we use for determining
- * forward references is presently quite incomplete.
+ * 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.
+ * 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.
  *
  * "schemaName" is the name of the schema that will be used for the creation
- * of the objects listed, that may be compiled from the schema name defined
+ * of the objects listed.  It may be obtained from the schema name defined
  * in the statement or a role specification.
  *
- * SQL also allows constraints to make forward references, so thumb through
- * the table columns and move forward references to a posterior alter-table
- * command.
- *
  * 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: this breaks the rules a little bit by modifying schema-name fields
- * within passed-in structs.  However, the transformation would be the same
- * if done over, so it should be all right to scribble on the input to this
- * extent.
  */
 List *
-transformCreateSchemaStmtElements(List *schemaElts, const char *schemaName)
+transformCreateSchemaStmtElements(ParseState *pstate, List *schemaElts,
+								  const char *schemaName)
 {
-	CreateSchemaStmtContext cxt;
-	List	   *result;
-	ListCell   *elements;
-
-	cxt.schemaname = schemaName;
-	cxt.sequences = NIL;
-	cxt.tables = NIL;
-	cxt.views = NIL;
-	cxt.indexes = NIL;
-	cxt.triggers = NIL;
-	cxt.grants = NIL;
+	List	   *elements = NIL;
+	ListCell   *lc;
 
 	/*
-	 * Run through each schema element in the schema element list. Separate
-	 * statements by type, and do preliminary analysis.
+	 * Run through each schema element in the schema element list.  Check
+	 * target schema names, and collect the list of actions to be done.
 	 */
-	foreach(elements, schemaElts)
+	foreach(lc, schemaElts)
 	{
-		Node	   *element = lfirst(elements);
+		Node	   *element = lfirst(lc);
 
 		switch (nodeTag(element))
 		{
@@ -4447,8 +4420,8 @@ transformCreateSchemaStmtElements(List *schemaElts, const char *schemaName)
 				{
 					CreateSeqStmt *elp = (CreateSeqStmt *) element;
 
-					setSchemaName(cxt.schemaname, &elp->sequence->schemaname);
-					cxt.sequences = lappend(cxt.sequences, element);
+					checkSchemaName(pstate, schemaName, elp->sequence);
+					elements = lappend(elements, element);
 				}
 				break;
 
@@ -4456,12 +4429,8 @@ transformCreateSchemaStmtElements(List *schemaElts, const char *schemaName)
 				{
 					CreateStmt *elp = (CreateStmt *) element;
 
-					setSchemaName(cxt.schemaname, &elp->relation->schemaname);
-
-					/*
-					 * XXX todo: deal with constraints
-					 */
-					cxt.tables = lappend(cxt.tables, element);
+					checkSchemaName(pstate, schemaName, elp->relation);
+					elements = lappend(elements, element);
 				}
 				break;
 
@@ -4469,12 +4438,8 @@ transformCreateSchemaStmtElements(List *schemaElts, const char *schemaName)
 				{
 					ViewStmt   *elp = (ViewStmt *) element;
 
-					setSchemaName(cxt.schemaname, &elp->view->schemaname);
-
-					/*
-					 * XXX todo: deal with references between views
-					 */
-					cxt.views = lappend(cxt.views, element);
+					checkSchemaName(pstate, schemaName, elp->view);
+					elements = lappend(elements, element);
 				}
 				break;
 
@@ -4482,8 +4447,8 @@ transformCreateSchemaStmtElements(List *schemaElts, const char *schemaName)
 				{
 					IndexStmt  *elp = (IndexStmt *) element;
 
-					setSchemaName(cxt.schemaname, &elp->relation->schemaname);
-					cxt.indexes = lappend(cxt.indexes, element);
+					checkSchemaName(pstate, schemaName, elp->relation);
+					elements = lappend(elements, element);
 				}
 				break;
 
@@ -4491,13 +4456,13 @@ transformCreateSchemaStmtElements(List *schemaElts, const char *schemaName)
 				{
 					CreateTrigStmt *elp = (CreateTrigStmt *) element;
 
-					setSchemaName(cxt.schemaname, &elp->relation->schemaname);
-					cxt.triggers = lappend(cxt.triggers, element);
+					checkSchemaName(pstate, schemaName, elp->relation);
+					elements = lappend(elements, element);
 				}
 				break;
 
 			case T_GrantStmt:
-				cxt.grants = lappend(cxt.grants, element);
+				elements = lappend(elements, element);
 				break;
 
 			default:
@@ -4506,32 +4471,39 @@ transformCreateSchemaStmtElements(List *schemaElts, const char *schemaName)
 		}
 	}
 
-	result = NIL;
-	result = list_concat(result, cxt.sequences);
-	result = list_concat(result, cxt.tables);
-	result = list_concat(result, cxt.views);
-	result = list_concat(result, cxt.indexes);
-	result = list_concat(result, cxt.triggers);
-	result = list_concat(result, cxt.grants);
-
-	return result;
+	return elements;
 }
 
 /*
- * setSchemaName
- *		Set or check schema name in an element of a CREATE SCHEMA command
+ * checkSchemaName
+ *		Check schema name in an element of a CREATE SCHEMA command
+ *
+ * It's okay if the command doesn't specify a target schema name, because
+ * CreateSchemaCommand will set up the default creation schema to be the
+ * new schema.  But if a target schema name is given, it had better match.
+ * We also have to check that the command doesn't say CREATE TEMP, since
+ * that would likewise put the object into the wrong schema.
  */
 static void
-setSchemaName(const char *context_schema, char **stmt_schema_name)
+checkSchemaName(ParseState *pstate, const char *context_schema,
+				RangeVar *relation)
 {
-	if (*stmt_schema_name == NULL)
-		*stmt_schema_name = unconstify(char *, context_schema);
-	else if (strcmp(context_schema, *stmt_schema_name) != 0)
+	if (relation->schemaname != NULL &&
+		strcmp(context_schema, relation->schemaname) != 0)
 		ereport(ERROR,
-				(errcode(ERRCODE_INVALID_SCHEMA_DEFINITION),
-				 errmsg("CREATE specifies a schema (%s) "
-						"different from the one being created (%s)",
-						*stmt_schema_name, context_schema)));
+				errcode(ERRCODE_INVALID_SCHEMA_DEFINITION),
+				errmsg("CREATE specifies a schema (%s) different from the one being created (%s)",
+					   relation->schemaname, context_schema),
+				parser_errposition(pstate, relation->location));
+
+	if (relation->relpersistence == RELPERSISTENCE_TEMP)
+	{
+		/* spell this error the same as in RangeVarAdjustRelationPersistence */
+		ereport(ERROR,
+				errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+				errmsg("cannot create temporary relation in non-temporary schema"),
+				parser_errposition(pstate, relation->location));
+	}
 }
 
 /*
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 2b609bfc824..6ff30f0d06f 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1122,8 +1122,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				 * relation and attribute manipulation
 				 */
 			case T_CreateSchemaStmt:
-				CreateSchemaCommand((CreateSchemaStmt *) parsetree,
-									queryString,
+				CreateSchemaCommand(pstate, castNode(CreateSchemaStmt, parsetree),
 									pstmt->stmt_location,
 									pstmt->stmt_len);
 
diff --git a/src/include/commands/schemacmds.h b/src/include/commands/schemacmds.h
index d100e4691b3..559727be5de 100644
--- a/src/include/commands/schemacmds.h
+++ b/src/include/commands/schemacmds.h
@@ -16,12 +16,9 @@
 #define SCHEMACMDS_H
 
 #include "catalog/objectaddress.h"
-#include "nodes/parsenodes.h"
-
-extern Oid	CreateSchemaCommand(CreateSchemaStmt *stmt,
-								const char *queryString,
-								int stmt_location, int stmt_len);
+#include "parser/parse_node.h"
 
+extern Oid	CreateSchemaCommand(ParseState *pstate, CreateSchemaStmt *stmt, int stmt_location, int stmt_len);
 extern ObjectAddress RenameSchema(const char *oldname, const char *newname);
 extern ObjectAddress AlterSchemaOwner(const char *name, Oid newOwnerId);
 extern void AlterSchemaOwner_oid(Oid schemaoid, Oid newOwnerId);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 53f282f77ec..34c98e5122f 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -30,7 +30,8 @@ extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-extern List *transformCreateSchemaStmtElements(List *schemaElts,
+extern List *transformCreateSchemaStmtElements(ParseState *pstate,
+											   List *schemaElts,
 											   const char *schemaName);
 extern PartitionBoundSpec *transformPartitionBound(ParseState *pstate, Relation parent,
 												   PartitionBoundSpec *spec);
diff --git a/src/test/regress/expected/create_schema.out b/src/test/regress/expected/create_schema.out
index 93302a07efc..38530c282a9 100644
--- a/src/test/regress/expected/create_schema.out
+++ b/src/test/regress/expected/create_schema.out
@@ -9,55 +9,92 @@ CREATE ROLE regress_create_schema_role SUPERUSER;
 CREATE SCHEMA AUTHORIZATION regress_create_schema_role
   CREATE SEQUENCE schema_not_existing.seq;
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_create_schema_role)
+LINE 2:   CREATE SEQUENCE schema_not_existing.seq;
+                          ^
 CREATE SCHEMA AUTHORIZATION regress_create_schema_role
   CREATE TABLE schema_not_existing.tab (id int);
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_create_schema_role)
+LINE 2:   CREATE TABLE schema_not_existing.tab (id int);
+                       ^
 CREATE SCHEMA AUTHORIZATION regress_create_schema_role
   CREATE VIEW schema_not_existing.view AS SELECT 1;
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_create_schema_role)
+LINE 2:   CREATE VIEW schema_not_existing.view AS SELECT 1;
+                      ^
 CREATE SCHEMA AUTHORIZATION regress_create_schema_role
   CREATE INDEX ON schema_not_existing.tab (id);
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_create_schema_role)
+LINE 2:   CREATE INDEX ON schema_not_existing.tab (id);
+                          ^
 CREATE SCHEMA AUTHORIZATION regress_create_schema_role
   CREATE TRIGGER schema_trig BEFORE INSERT ON schema_not_existing.tab
   EXECUTE FUNCTION schema_trig.no_func();
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_create_schema_role)
+LINE 2:   CREATE TRIGGER schema_trig BEFORE INSERT ON schema_not_exi...
+                                                      ^
 -- Again, with a role specification and no schema names.
 SET ROLE regress_create_schema_role;
 CREATE SCHEMA AUTHORIZATION CURRENT_ROLE
   CREATE SEQUENCE schema_not_existing.seq;
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_create_schema_role)
+LINE 2:   CREATE SEQUENCE schema_not_existing.seq;
+                          ^
 CREATE SCHEMA AUTHORIZATION CURRENT_ROLE
   CREATE TABLE schema_not_existing.tab (id int);
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_create_schema_role)
+LINE 2:   CREATE TABLE schema_not_existing.tab (id int);
+                       ^
 CREATE SCHEMA AUTHORIZATION CURRENT_ROLE
   CREATE VIEW schema_not_existing.view AS SELECT 1;
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_create_schema_role)
+LINE 2:   CREATE VIEW schema_not_existing.view AS SELECT 1;
+                      ^
 CREATE SCHEMA AUTHORIZATION CURRENT_ROLE
   CREATE INDEX ON schema_not_existing.tab (id);
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_create_schema_role)
+LINE 2:   CREATE INDEX ON schema_not_existing.tab (id);
+                          ^
 CREATE SCHEMA AUTHORIZATION CURRENT_ROLE
   CREATE TRIGGER schema_trig BEFORE INSERT ON schema_not_existing.tab
   EXECUTE FUNCTION schema_trig.no_func();
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_create_schema_role)
+LINE 2:   CREATE TRIGGER schema_trig BEFORE INSERT ON schema_not_exi...
+                                                      ^
 -- Again, with a schema name and a role specification.
 CREATE SCHEMA regress_schema_1 AUTHORIZATION CURRENT_ROLE
   CREATE SEQUENCE schema_not_existing.seq;
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_schema_1)
+LINE 2:   CREATE SEQUENCE schema_not_existing.seq;
+                          ^
 CREATE SCHEMA regress_schema_1 AUTHORIZATION CURRENT_ROLE
   CREATE TABLE schema_not_existing.tab (id int);
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_schema_1)
+LINE 2:   CREATE TABLE schema_not_existing.tab (id int);
+                       ^
 CREATE SCHEMA regress_schema_1 AUTHORIZATION CURRENT_ROLE
   CREATE VIEW schema_not_existing.view AS SELECT 1;
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_schema_1)
+LINE 2:   CREATE VIEW schema_not_existing.view AS SELECT 1;
+                      ^
 CREATE SCHEMA regress_schema_1 AUTHORIZATION CURRENT_ROLE
   CREATE INDEX ON schema_not_existing.tab (id);
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_schema_1)
+LINE 2:   CREATE INDEX ON schema_not_existing.tab (id);
+                          ^
 CREATE SCHEMA regress_schema_1 AUTHORIZATION CURRENT_ROLE
   CREATE TRIGGER schema_trig BEFORE INSERT ON schema_not_existing.tab
   EXECUTE FUNCTION schema_trig.no_func();
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_schema_1)
+LINE 2:   CREATE TRIGGER schema_trig BEFORE INSERT ON schema_not_exi...
+                                                      ^
 RESET ROLE;
+--error. Forward reference is not allowed.
+CREATE SCHEMA regress_schema_2
+  CREATE VIEW abcd_view AS SELECT a FROM abcd
+  CREATE TABLE abcd (a int);
+ERROR:  relation "abcd" does not exist
+LINE 2:   CREATE VIEW abcd_view AS SELECT a FROM abcd
+                                                 ^
 -- Cases where the schema creation succeeds.
 -- The schema created matches the role name.
 CREATE SCHEMA AUTHORIZATION regress_create_schema_role
diff --git a/src/test/regress/expected/create_view.out b/src/test/regress/expected/create_view.out
index bc4f79938b3..63cf4b4371d 100644
--- a/src/test/regress/expected/create_view.out
+++ b/src/test/regress/expected/create_view.out
@@ -128,6 +128,8 @@ ERROR:  cannot create temporary relation in non-temporary schema
 CREATE SCHEMA test_view_schema
     CREATE TEMP VIEW testview AS SELECT 1;
 ERROR:  cannot create temporary relation in non-temporary schema
+LINE 2:     CREATE TEMP VIEW testview AS SELECT 1;
+                             ^
 -- joins: if any of the join relations are temporary, the view
 -- should also be temporary
 -- should be non-temp
diff --git a/src/test/regress/expected/event_trigger.out b/src/test/regress/expected/event_trigger.out
index f897b079e67..4c32e1dcaf5 100644
--- a/src/test/regress/expected/event_trigger.out
+++ b/src/test/regress/expected/event_trigger.out
@@ -425,13 +425,13 @@ NOTICE:  END: command_tag=CREATE TABLE type=table identity=evttrig.one
 NOTICE:  END: command_tag=CREATE INDEX type=index identity=evttrig.one_pkey
 NOTICE:  END: command_tag=ALTER SEQUENCE type=sequence identity=evttrig.one_col_a_seq
 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=CREATE INDEX type=index identity=evttrig.one_idx
 -- 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/expected/namespace.out b/src/test/regress/expected/namespace.out
index dbbda72d395..d02f3fd67d7 100644
--- a/src/test/regress/expected/namespace.out
+++ b/src/test/regress/expected/namespace.out
@@ -10,13 +10,10 @@ SELECT pg_catalog.set_config('search_path', ' ', false);
 (1 row)
 
 CREATE SCHEMA test_ns_schema_1
+       CREATE TABLE abc (a serial, b int UNIQUE)
        CREATE UNIQUE INDEX abc_a_idx ON abc (a)
        CREATE VIEW abc_view AS
-              SELECT a+1 AS a, b+1 AS b FROM abc
-       CREATE TABLE abc (
-              a serial,
-              b int UNIQUE
-       );
+              SELECT a+1 AS a, b+1 AS b FROM abc;
 -- verify that the correct search_path restored on abort
 SET search_path to public;
 BEGIN;
diff --git a/src/test/regress/sql/create_schema.sql b/src/test/regress/sql/create_schema.sql
index 1b7064247a1..b3dc1cfd758 100644
--- a/src/test/regress/sql/create_schema.sql
+++ b/src/test/regress/sql/create_schema.sql
@@ -47,6 +47,11 @@ CREATE SCHEMA regress_schema_1 AUTHORIZATION CURRENT_ROLE
   EXECUTE FUNCTION schema_trig.no_func();
 RESET ROLE;
 
+--error. Forward reference is not allowed.
+CREATE SCHEMA regress_schema_2
+  CREATE VIEW abcd_view AS SELECT a FROM abcd
+  CREATE TABLE abcd (a int);
+
 -- Cases where the schema creation succeeds.
 -- The schema created matches the role name.
 CREATE SCHEMA AUTHORIZATION regress_create_schema_role
diff --git a/src/test/regress/sql/namespace.sql b/src/test/regress/sql/namespace.sql
index 306cdc2d8c6..9433eb3c15c 100644
--- a/src/test/regress/sql/namespace.sql
+++ b/src/test/regress/sql/namespace.sql
@@ -7,15 +7,11 @@
 SELECT pg_catalog.set_config('search_path', ' ', false);
 
 CREATE SCHEMA test_ns_schema_1
+       CREATE TABLE abc (a serial, b int UNIQUE)
        CREATE UNIQUE INDEX abc_a_idx ON abc (a)
 
        CREATE VIEW abc_view AS
-              SELECT a+1 AS a, b+1 AS b FROM abc
-
-       CREATE TABLE abc (
-              a serial,
-              b int UNIQUE
-       );
+              SELECT a+1 AS a, b+1 AS b FROM abc;
 
 -- verify that the correct search_path restored on abort
 SET search_path to public;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index e3c1007abdf..1a9ffac5a93 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -581,7 +581,6 @@ CreateRangeStmt
 CreateReplicationSlotCmd
 CreateRoleStmt
 CreateSchemaStmt
-CreateSchemaStmtContext
 CreateSeqStmt
 CreateStatsStmt
 CreateStmt
-- 
2.34.1



  [text/x-patch] v11-0004-CREATE-SCHEMA-CREATE-TYPE.patch (15.9K, 5-v11-0004-CREATE-SCHEMA-CREATE-TYPE.patch)
  download | inline diff:
From 54f1c2c78ca2de4a325225569665de7d169bb485 Mon Sep 17 00:00:00 2001
From: jian he <[email protected]>
Date: Mon, 30 Mar 2026 09:43:39 +0800
Subject: [PATCH v11 4/5] CREATE SCHEMA CREATE TYPE

The SQL standard allows types to be specified in a CREATE SCHEMA statement.
This adds support for that capability.

For example:
CREATE SCHEMA schema_name CREATE TYPE ss;

The type will be created within the to be created schema.  The type name can be
schema-qualified or database-qualified, however it's not allowed to let type
create within a different schema.

Discussion: https://postgr.es/m/CALdSSPh4jUSDsWu3K58hjO60wnTRR0DuO4CKRcwa8EVuOSfXxg@mail.gmail.com
Commitfest: https://commitfest.postgresql.org/patch/5985
---
 doc/src/sgml/ref/create_schema.sgml           |  3 +-
 src/backend/parser/parse_utilcmd.c            | 52 ++++++++++--
 .../expected/create_schema.out                | 16 +++-
 .../test_ddl_deparse/sql/create_schema.sql    |  6 +-
 src/test/regress/expected/create_schema.out   | 83 +++++++++++++++++++
 src/test/regress/expected/create_type.out     | 12 +++
 src/test/regress/sql/create_schema.sql        | 37 +++++++++
 src/test/regress/sql/create_type.sql          | 12 +++
 8 files changed, 209 insertions(+), 12 deletions(-)

diff --git a/doc/src/sgml/ref/create_schema.sgml b/doc/src/sgml/ref/create_schema.sgml
index d8273bb2d0c..905e966e30e 100644
--- a/doc/src/sgml/ref/create_schema.sgml
+++ b/doc/src/sgml/ref/create_schema.sgml
@@ -104,7 +104,8 @@ CREATE SCHEMA IF NOT EXISTS AUTHORIZATION <replaceable class="parameter">role_sp
         <command>CREATE DOMAIN</command>
         <command>CREATE TABLE</command>, <command>CREATE VIEW</command>, <command>CREATE
         INDEX</command>, <command>CREATE SEQUENCE</command>, <command>CREATE
-        TRIGGER</command> and <command>GRANT</command> are accepted as clauses
+        TRIGGER</command>, <command>CREATE TYPE</command> and
+        <command>GRANT</command> are accepted as clauses
         within <command>CREATE SCHEMA</command>. Other kinds of objects may
         be created in separate commands after the schema is created.
        </para>
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 01d05a8c124..86fe8652093 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -4496,7 +4496,7 @@ transformCreateSchemaStmtElements(ParseState *pstate, List *schemaElts,
 					obj_type = asc_toupper(stringify_objtype(stmt->kind),
 										   strlen(stringify_objtype(stmt->kind)));
 
-					if (stmt->kind != OBJECT_COLLATION)
+					if (stmt->kind != OBJECT_COLLATION && stmt->kind != OBJECT_TYPE)
 						ereport(ERROR,
 								errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 								errmsg("CREATE SCHEMA ... CREATE %s currently not supported", obj_type));
@@ -4513,16 +4513,52 @@ transformCreateSchemaStmtElements(ParseState *pstate, List *schemaElts,
 				}
 				break;
 
-				/*
-				 * gram.y classifies these as DefineStmt as well; therefore,
-				 * we must explicitly raise an error for these cases.
-				 */
 			case T_CompositeTypeStmt:
+				{
+					CompositeTypeStmt *stmt = castNode(CompositeTypeStmt, element);
+
+					checkSchemaName(pstate, schemaName, stmt->typevar);
+
+					elements = lappend(elements, element);
+				}
+				break;
+
 			case T_CreateEnumStmt:
+				{
+					char	   *typschema = NULL;
+					char	   *typname;
+
+					CreateEnumStmt *stmt = castNode(CreateEnumStmt, element);
+
+					DeconstructQualifiedName(stmt->typeName, &typschema, &typname);
+
+					if (typschema && strcmp(typschema, schemaName) != 0)
+						ereport(ERROR,
+								errcode(ERRCODE_INVALID_SCHEMA_DEFINITION),
+								errmsg("CREATE TYPE specifies a schema (%s) different from the one being created (%s)",
+									   typschema, schemaName));
+
+					elements = lappend(elements, element);
+				}
+				break;
+
 			case T_CreateRangeStmt:
-				ereport(ERROR,
-						errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-						errmsg("CREATE SCHEMA ... CREATE TYPE currently not supported"));
+				{
+					char	   *typschema = NULL;
+					char	   *typname;
+
+					CreateRangeStmt *stmt = castNode(CreateRangeStmt, element);
+
+					DeconstructQualifiedName(stmt->typeName, &typschema, &typname);
+
+					if (typschema && strcmp(typschema, schemaName) != 0)
+						ereport(ERROR,
+								errcode(ERRCODE_INVALID_SCHEMA_DEFINITION),
+								errmsg("CREATE TYPE specifies a schema (%s) different from the one being created (%s)",
+									   schemaName, typschema));
+
+					elements = lappend(elements, element);
+				}
 				break;
 			default:
 				elog(ERROR, "unrecognized node type: %d",
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 e3c197af118..92da27c813b 100644
--- a/src/test/modules/test_ddl_deparse/expected/create_schema.out
+++ b/src/test/modules/test_ddl_deparse/expected/create_schema.out
@@ -15,15 +15,27 @@ CREATE SCHEMA element_test
   CREATE TABLE foo (id int)
   CREATE VIEW bar AS SELECT * FROM foo
   CREATE DOMAIN d1 AS INT
-  CREATE COLLATION coll (LOCALE="C");
+  CREATE COLLATION coll (LOCALE="C")
+  CREATE TYPE floatrange AS RANGE (subtype = float8, subtype_diff = float8mi)
+  CREATE TYPE ss AS (a int)
+  CREATE TYPE sss
+  CREATE TYPE rainbow AS ENUM ('red', 'orange');
 NOTICE:  DDL test: type simple, tag CREATE SCHEMA
 NOTICE:  DDL test: type simple, tag CREATE TABLE
 NOTICE:  DDL test: type simple, tag CREATE VIEW
 NOTICE:  DDL test: type simple, tag CREATE DOMAIN
 NOTICE:  DDL test: type simple, tag CREATE COLLATION
+NOTICE:  DDL test: type simple, tag CREATE TYPE
+NOTICE:  DDL test: type simple, tag CREATE TYPE
+NOTICE:  DDL test: type simple, tag CREATE TYPE
+NOTICE:  DDL test: type simple, tag CREATE TYPE
 DROP SCHEMA element_test CASCADE;
-NOTICE:  drop cascades to 4 other objects
+NOTICE:  drop cascades to 8 other objects
 DETAIL:  drop cascades to table element_test.foo
 drop cascades to view element_test.bar
 drop cascades to type element_test.d1
 drop cascades to collation element_test.coll
+drop cascades to type element_test.floatrange
+drop cascades to type element_test.ss
+drop cascades to type element_test.sss
+drop cascades to type element_test.rainbow
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 0c61db13935..abe0637c415 100644
--- a/src/test/modules/test_ddl_deparse/sql/create_schema.sql
+++ b/src/test/modules/test_ddl_deparse/sql/create_schema.sql
@@ -16,6 +16,10 @@ CREATE SCHEMA element_test
   CREATE TABLE foo (id int)
   CREATE VIEW bar AS SELECT * FROM foo
   CREATE DOMAIN d1 AS INT
-  CREATE COLLATION coll (LOCALE="C");
+  CREATE COLLATION coll (LOCALE="C")
+  CREATE TYPE floatrange AS RANGE (subtype = float8, subtype_diff = float8mi)
+  CREATE TYPE ss AS (a int)
+  CREATE TYPE sss
+  CREATE TYPE rainbow AS ENUM ('red', 'orange');
 
 DROP SCHEMA element_test CASCADE;
diff --git a/src/test/regress/expected/create_schema.out b/src/test/regress/expected/create_schema.out
index 34d36a6b3f2..a15bdc67687 100644
--- a/src/test/regress/expected/create_schema.out
+++ b/src/test/regress/expected/create_schema.out
@@ -207,6 +207,76 @@ CREATE SCHEMA regress_schema_5 AUTHORIZATION CURRENT_ROLE
  regress_schema_5 | coll | libc     | C       | C     |        |           | yes
 (1 row)
 
+-- CREATE SCHEMA CREATE TYPE
+-- fail, cannot CREATE TYPE to other schema
+CREATE SCHEMA regress_schema_6 CREATE TYPE public.ss AS (a int);
+ERROR:  CREATE specifies a schema (public) different from the one being created (regress_schema_6)
+LINE 1: CREATE SCHEMA regress_schema_6 CREATE TYPE public.ss AS (a i...
+                                                   ^
+CREATE SCHEMA regress_schema_6 CREATE TYPE public.ss;
+ERROR:  CREATE TYPE specifies a schema (public) different from the one being created (regress_schema_6)
+CREATE SCHEMA regress_schema_6 CREATE TYPE public.rainbow AS ENUM ('red', 'orange');
+ERROR:  CREATE TYPE specifies a schema (public) different from the one being created (regress_schema_6)
+CREATE SCHEMA regress_schema_6 CREATE TYPE public.floatrange AS RANGE (subtype = float8, subtype_diff = float8mi);
+ERROR:  CREATE TYPE specifies a schema (regress_schema_6) different from the one being created (public)
+-- fail, improper qualified name
+CREATE SCHEMA regress_schema_6 AUTHORIZATION CURRENT_ROLE
+  CREATE TYPE postgres.public.floatrange AS RANGE (subtype = float8, subtype_diff = float8mi);
+ERROR:  cross-database references are not implemented: postgres.public.floatrange
+-- fail, because we execute subcommands in order
+CREATE SCHEMA regress_schema_6 CREATE TABLE tts(a ss) CREATE TYPE ss;
+ERROR:  type "ss" does not exist
+LINE 1: CREATE SCHEMA regress_schema_6 CREATE TABLE tts(a ss) CREATE...
+                                                          ^
+CREATE SCHEMA regress_schema_6 CREATE TABLE tts(a ss) CREATE TYPE ss AS (a int);
+ERROR:  type "ss" does not exist
+LINE 1: CREATE SCHEMA regress_schema_6 CREATE TABLE tts(a ss) CREATE...
+                                                          ^
+CREATE SCHEMA regress_schema_6 CREATE TABLE tts(a rainbow) CREATE TYPE rainbow AS ENUM ('red', 'orange');
+ERROR:  type "rainbow" does not exist
+LINE 1: CREATE SCHEMA regress_schema_6 CREATE TABLE tts(a rainbow) C...
+                                                          ^
+CREATE SCHEMA regress_schema_6
+  CREATE TABLE tts(a floatrange)
+  CREATE TYPE floatrange AS RANGE (subtype = float8, subtype_diff = float8mi);
+ERROR:  type "floatrange" does not exist
+LINE 2:   CREATE TABLE tts(a floatrange)
+                             ^
+-- ok
+CREATE SCHEMA regress_schema_6
+  CREATE TYPE floatrange AS RANGE (subtype = float8, subtype_diff = float8mi)
+  CREATE TYPE ss AS (a int)
+  CREATE TYPE sss
+  CREATE TYPE rainbow AS ENUM ('red', 'orange');
+\dT regress_schema_6.*
+                        List of data types
+      Schema      |               Name               | Description 
+------------------+----------------------------------+-------------
+ regress_schema_6 | regress_schema_6.floatmultirange | 
+ regress_schema_6 | regress_schema_6.floatrange      | 
+ regress_schema_6 | regress_schema_6.rainbow         | 
+ regress_schema_6 | regress_schema_6.ss              | 
+ regress_schema_6 | regress_schema_6.sss             | 
+(5 rows)
+
+-- schema qualified, ok
+CREATE SCHEMA regress_schema_7
+  CREATE TYPE regress_schema_7.floatrange AS RANGE (subtype = float8, subtype_diff = float8mi)
+  CREATE TYPE regress_schema_7.ss AS (a int)
+  CREATE TYPE regress_schema_7.sss
+  CREATE TYPE regress_schema_7.rainbow AS ENUM ('red', 'orange')
+  CREATE TABLE t(a floatrange, b ss, c rainbow);
+\dT regress_schema_7.*
+                        List of data types
+      Schema      |               Name               | Description 
+------------------+----------------------------------+-------------
+ regress_schema_7 | regress_schema_7.floatmultirange | 
+ regress_schema_7 | regress_schema_7.floatrange      | 
+ regress_schema_7 | regress_schema_7.rainbow         | 
+ regress_schema_7 | regress_schema_7.ss              | 
+ regress_schema_7 | regress_schema_7.sss             | 
+(5 rows)
+
 DROP SCHEMA regress_schema_2 CASCADE;
 NOTICE:  drop cascades to 2 other objects
 DETAIL:  drop cascades to type regress_schema_2.ss
@@ -225,5 +295,18 @@ DROP SCHEMA regress_schema_5 CASCADE;
 NOTICE:  drop cascades to 2 other objects
 DETAIL:  drop cascades to collation regress_schema_5.coll
 drop cascades to table regress_schema_5.t
+DROP SCHEMA regress_schema_6 CASCADE;
+NOTICE:  drop cascades to 4 other objects
+DETAIL:  drop cascades to type regress_schema_6.floatrange
+drop cascades to type regress_schema_6.ss
+drop cascades to type regress_schema_6.sss
+drop cascades to type regress_schema_6.rainbow
+DROP SCHEMA regress_schema_7 CASCADE;
+NOTICE:  drop cascades to 5 other objects
+DETAIL:  drop cascades to type regress_schema_7.floatrange
+drop cascades to type regress_schema_7.ss
+drop cascades to type regress_schema_7.sss
+drop cascades to type regress_schema_7.rainbow
+drop cascades to table regress_schema_7.t
 -- Clean up
 DROP ROLE regress_create_schema_role;
diff --git a/src/test/regress/expected/create_type.out b/src/test/regress/expected/create_type.out
index 5181c4290b4..fdbed6f99e0 100644
--- a/src/test/regress/expected/create_type.out
+++ b/src/test/regress/expected/create_type.out
@@ -35,6 +35,18 @@ CREATE FUNCTION int44out(city_budget)
 NOTICE:  argument type city_budget is only a shell
 LINE 1: CREATE FUNCTION int44out(city_budget)
                                  ^
+-- CREATE SCHEMA CREATE TYPE
+-- error, cannot CREATE TYPE to other schema
+CREATE SCHEMA regress_schema_x
+   CREATE TYPE public.widget (
+      internallength = 24,
+      input = widget_in,
+      output = widget_out,
+      typmod_in = numerictypmodin,
+      typmod_out = numerictypmodout,
+      alignment = double
+   );
+ERROR:  CREATE TYPE specifies a schema (public) different from the one being created (regress_schema_x)
 CREATE TYPE widget (
    internallength = 24,
    input = widget_in,
diff --git a/src/test/regress/sql/create_schema.sql b/src/test/regress/sql/create_schema.sql
index f7f28363b4a..b1c837d27ce 100644
--- a/src/test/regress/sql/create_schema.sql
+++ b/src/test/regress/sql/create_schema.sql
@@ -125,11 +125,48 @@ CREATE SCHEMA regress_schema_5 AUTHORIZATION CURRENT_ROLE
   CREATE TABLE t(a TEXT COLLATE coll);
 \dO regress_schema_5.*
 
+-- CREATE SCHEMA CREATE TYPE
+-- fail, cannot CREATE TYPE to other schema
+CREATE SCHEMA regress_schema_6 CREATE TYPE public.ss AS (a int);
+CREATE SCHEMA regress_schema_6 CREATE TYPE public.ss;
+CREATE SCHEMA regress_schema_6 CREATE TYPE public.rainbow AS ENUM ('red', 'orange');
+CREATE SCHEMA regress_schema_6 CREATE TYPE public.floatrange AS RANGE (subtype = float8, subtype_diff = float8mi);
+
+-- fail, improper qualified name
+CREATE SCHEMA regress_schema_6 AUTHORIZATION CURRENT_ROLE
+  CREATE TYPE postgres.public.floatrange AS RANGE (subtype = float8, subtype_diff = float8mi);
+
+-- fail, because we execute subcommands in order
+CREATE SCHEMA regress_schema_6 CREATE TABLE tts(a ss) CREATE TYPE ss;
+CREATE SCHEMA regress_schema_6 CREATE TABLE tts(a ss) CREATE TYPE ss AS (a int);
+CREATE SCHEMA regress_schema_6 CREATE TABLE tts(a rainbow) CREATE TYPE rainbow AS ENUM ('red', 'orange');
+CREATE SCHEMA regress_schema_6
+  CREATE TABLE tts(a floatrange)
+  CREATE TYPE floatrange AS RANGE (subtype = float8, subtype_diff = float8mi);
+
+-- ok
+CREATE SCHEMA regress_schema_6
+  CREATE TYPE floatrange AS RANGE (subtype = float8, subtype_diff = float8mi)
+  CREATE TYPE ss AS (a int)
+  CREATE TYPE sss
+  CREATE TYPE rainbow AS ENUM ('red', 'orange');
+\dT regress_schema_6.*
+
+-- schema qualified, ok
+CREATE SCHEMA regress_schema_7
+  CREATE TYPE regress_schema_7.floatrange AS RANGE (subtype = float8, subtype_diff = float8mi)
+  CREATE TYPE regress_schema_7.ss AS (a int)
+  CREATE TYPE regress_schema_7.sss
+  CREATE TYPE regress_schema_7.rainbow AS ENUM ('red', 'orange')
+  CREATE TABLE t(a floatrange, b ss, c rainbow);
+\dT regress_schema_7.*
 
 DROP SCHEMA regress_schema_2 CASCADE;
 DROP SCHEMA regress_schema_3 CASCADE;
 DROP SCHEMA regress_schema_4 CASCADE;
 DROP SCHEMA regress_schema_5 CASCADE;
+DROP SCHEMA regress_schema_6 CASCADE;
+DROP SCHEMA regress_schema_7 CASCADE;
 
 -- Clean up
 DROP ROLE regress_create_schema_role;
diff --git a/src/test/regress/sql/create_type.sql b/src/test/regress/sql/create_type.sql
index c25018029c2..4c2cb35a01f 100644
--- a/src/test/regress/sql/create_type.sql
+++ b/src/test/regress/sql/create_type.sql
@@ -32,6 +32,18 @@ CREATE FUNCTION int44out(city_budget)
    AS :'regresslib'
    LANGUAGE C STRICT IMMUTABLE;
 
+-- CREATE SCHEMA CREATE TYPE
+-- error, cannot CREATE TYPE to other schema
+CREATE SCHEMA regress_schema_x
+   CREATE TYPE public.widget (
+      internallength = 24,
+      input = widget_in,
+      output = widget_out,
+      typmod_in = numerictypmodin,
+      typmod_out = numerictypmodout,
+      alignment = double
+   );
+
 CREATE TYPE widget (
    internallength = 24,
    input = widget_in,
-- 
2.34.1



  [text/x-patch] v11-0003-CREATE-SCHEMA-CREATE-COLLATION.patch (14.3K, 6-v11-0003-CREATE-SCHEMA-CREATE-COLLATION.patch)
  download | inline diff:
From 48cf5f3a3fc8fd17f712c47d49fc2464bb209527 Mon Sep 17 00:00:00 2001
From: jian he <[email protected]>
Date: Mon, 30 Mar 2026 09:33:33 +0800
Subject: [PATCH v11 3/5] CREATE SCHEMA CREATE COLLATION

The SQL standard allows collation to be specified in a CREATE SCHEMA statement.
This adds support for that capability.

For example:
    CREATE SCHEMA schema_name CREATE COLLATION coll_icu_und FROM "und-x-icu";

The collation will be created within the to be created schema.  The collation
name can be schema-qualified or database-qualified, however it's not allowed to
let collation create within a different schema.

Discussion: https://postgr.es/m/CALdSSPh4jUSDsWu3K58hjO60wnTRR0DuO4CKRcwa8EVuOSfXxg@mail.gmail.com
Commitfest: https://commitfest.postgresql.org/patch/5985
---
 doc/src/sgml/ref/create_schema.sgml           |  3 +-
 src/backend/catalog/objectaddress.c           | 18 ++++++++
 src/backend/parser/gram.y                     |  1 +
 src/backend/parser/parse_utilcmd.c            | 40 +++++++++++++++++
 src/include/catalog/objectaddress.h           |  1 +
 .../expected/create_schema.out                |  7 ++-
 .../test_ddl_deparse/sql/create_schema.sql    |  3 +-
 .../regress/expected/collate.icu.utf8.out     | 15 +++++++
 src/test/regress/expected/create_schema.out   | 43 +++++++++++++++++++
 src/test/regress/sql/collate.icu.utf8.sql     |  7 +++
 src/test/regress/sql/create_schema.sql        | 29 +++++++++++++
 11 files changed, 163 insertions(+), 4 deletions(-)

diff --git a/doc/src/sgml/ref/create_schema.sgml b/doc/src/sgml/ref/create_schema.sgml
index 79186d2b936..d8273bb2d0c 100644
--- a/doc/src/sgml/ref/create_schema.sgml
+++ b/doc/src/sgml/ref/create_schema.sgml
@@ -100,7 +100,8 @@ CREATE SCHEMA IF NOT EXISTS AUTHORIZATION <replaceable class="parameter">role_sp
       <listitem>
        <para>
         An SQL statement defining an object to be created within the
-        schema. Currently, only <command>CREATE DOMAIN</command>
+        schema. Currently, only <command>CREATE COLLATION</command>,
+        <command>CREATE DOMAIN</command>
         <command>CREATE TABLE</command>, <command>CREATE VIEW</command>, <command>CREATE
         INDEX</command>, <command>CREATE SEQUENCE</command>, <command>CREATE
         TRIGGER</command> and <command>GRANT</command> are accepted as clauses
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 7c93f5240ed..19eed5f8572 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -2719,6 +2719,24 @@ read_objtype_from_string(const char *objtype)
 	return -1;					/* keep compiler quiet */
 }
 
+/* get the ObjectType name */
+const char *
+stringify_objtype(ObjectType objtype)
+{
+	for (int i = 0; i < lengthof(ObjectTypeMap); i++)
+	{
+		if (ObjectTypeMap[i].tm_type == objtype)
+			return ObjectTypeMap[i].tm_name;
+	}
+
+	ereport(ERROR,
+			errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+			errmsg("unrecognized object type %d", objtype));
+
+	return NULL;				/* keep compiler quiet */
+}
+
+
 /*
  * Interfaces to reference fields of ObjectPropertyType
  */
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 66fb186cf93..825d85d8df8 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -1679,6 +1679,7 @@ schema_stmt:
 			| GrantStmt
 			| ViewStmt
 			| CreateDomainStmt
+			| DefineStmt
 		;
 
 
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 051a2d0b1d6..01d05a8c124 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -65,6 +65,7 @@
 #include "rewrite/rewriteManip.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
+#include "utils/formatting.h"
 #include "utils/lsyscache.h"
 #include "utils/partcache.h"
 #include "utils/rel.h"
@@ -4484,6 +4485,45 @@ transformCreateSchemaStmtElements(ParseState *pstate, List *schemaElts,
 				}
 				break;
 
+			case T_DefineStmt:
+				{
+					char	   *coll_schema = NULL;
+					char	   *collName;
+					char	   *obj_type;
+
+					DefineStmt *stmt = castNode(DefineStmt, element);
+
+					obj_type = asc_toupper(stringify_objtype(stmt->kind),
+										   strlen(stringify_objtype(stmt->kind)));
+
+					if (stmt->kind != OBJECT_COLLATION)
+						ereport(ERROR,
+								errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								errmsg("CREATE SCHEMA ... CREATE %s currently not supported", obj_type));
+
+					DeconstructQualifiedName(stmt->defnames, &coll_schema, &collName);
+
+					if (coll_schema && strcmp(coll_schema, schemaName) != 0)
+						ereport(ERROR,
+								errcode(ERRCODE_INVALID_SCHEMA_DEFINITION),
+								errmsg("CREATE %s specifies a schema (%s) different from the one being created (%s)",
+									   obj_type, coll_schema, schemaName));
+
+					elements = lappend(elements, element);
+				}
+				break;
+
+				/*
+				 * gram.y classifies these as DefineStmt as well; therefore,
+				 * we must explicitly raise an error for these cases.
+				 */
+			case T_CompositeTypeStmt:
+			case T_CreateEnumStmt:
+			case T_CreateRangeStmt:
+				ereport(ERROR,
+						errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						errmsg("CREATE SCHEMA ... CREATE TYPE currently not supported"));
+				break;
 			default:
 				elog(ERROR, "unrecognized node type: %d",
 					 (int) nodeTag(element));
diff --git a/src/include/catalog/objectaddress.h b/src/include/catalog/objectaddress.h
index b549be2d523..20d52ee589a 100644
--- a/src/include/catalog/objectaddress.h
+++ b/src/include/catalog/objectaddress.h
@@ -80,6 +80,7 @@ extern char *getObjectDescription(const ObjectAddress *object,
 extern char *getObjectDescriptionOids(Oid classid, Oid objid);
 
 extern int	read_objtype_from_string(const char *objtype);
+extern const char *stringify_objtype(ObjectType objtype);
 extern char *getObjectTypeDescription(const ObjectAddress *object,
 									  bool missing_ok);
 extern char *getObjectIdentity(const ObjectAddress *object,
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 634900d8456..e3c197af118 100644
--- a/src/test/modules/test_ddl_deparse/expected/create_schema.out
+++ b/src/test/modules/test_ddl_deparse/expected/create_schema.out
@@ -14,13 +14,16 @@ NOTICE:  schema "baz" already exists, skipping
 CREATE SCHEMA element_test
   CREATE TABLE foo (id int)
   CREATE VIEW bar AS SELECT * FROM foo
-  CREATE DOMAIN d1 AS INT;
+  CREATE DOMAIN d1 AS INT
+  CREATE COLLATION coll (LOCALE="C");
 NOTICE:  DDL test: type simple, tag CREATE SCHEMA
 NOTICE:  DDL test: type simple, tag CREATE TABLE
 NOTICE:  DDL test: type simple, tag CREATE VIEW
 NOTICE:  DDL test: type simple, tag CREATE DOMAIN
+NOTICE:  DDL test: type simple, tag CREATE COLLATION
 DROP SCHEMA element_test CASCADE;
-NOTICE:  drop cascades to 3 other objects
+NOTICE:  drop cascades to 4 other objects
 DETAIL:  drop cascades to table element_test.foo
 drop cascades to view element_test.bar
 drop cascades to type element_test.d1
+drop cascades to collation element_test.coll
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 b822517b64d..0c61db13935 100644
--- a/src/test/modules/test_ddl_deparse/sql/create_schema.sql
+++ b/src/test/modules/test_ddl_deparse/sql/create_schema.sql
@@ -15,6 +15,7 @@ CREATE SCHEMA IF NOT EXISTS baz;
 CREATE SCHEMA element_test
   CREATE TABLE foo (id int)
   CREATE VIEW bar AS SELECT * FROM foo
-  CREATE DOMAIN d1 AS INT;
+  CREATE DOMAIN d1 AS INT
+  CREATE COLLATION coll (LOCALE="C");
 
 DROP SCHEMA element_test CASCADE;
diff --git a/src/test/regress/expected/collate.icu.utf8.out b/src/test/regress/expected/collate.icu.utf8.out
index d170e7da066..9e323026bcf 100644
--- a/src/test/regress/expected/collate.icu.utf8.out
+++ b/src/test/regress/expected/collate.icu.utf8.out
@@ -1305,6 +1305,21 @@ SELECT 'a' = 'à' COLLATE strength_in_rule;  -- true because of the rule
  t
 (1 row)
 
+-- CREATE SCHEMA CREATE COLLATION
+CREATE SCHEMA regress_schema_4
+  CREATE COLLATION coll_icu_und FROM "und-x-icu"
+  CREATE TABLE tts(a TEXT COLLATE coll_icu_und);
+\dO regress_schema_4.*
+                                         List of collations
+      Schema      |     Name     | Provider | Collate | Ctype | Locale | ICU Rules | Deterministic? 
+------------------+--------------+----------+---------+-------+--------+-----------+----------------
+ regress_schema_4 | coll_icu_und | icu      |         |       | und    |           | yes
+(1 row)
+
+DROP SCHEMA regress_schema_4 CASCADE;
+NOTICE:  drop cascades to 2 other objects
+DETAIL:  drop cascades to collation regress_schema_4.coll_icu_und
+drop cascades to table regress_schema_4.tts
 -- nondeterministic collations
 CREATE COLLATION ctest_det (provider = icu, locale = '', deterministic = true);
 NOTICE:  using standard form "und" for ICU locale ""
diff --git a/src/test/regress/expected/create_schema.out b/src/test/regress/expected/create_schema.out
index 353a111ba95..34d36a6b3f2 100644
--- a/src/test/regress/expected/create_schema.out
+++ b/src/test/regress/expected/create_schema.out
@@ -172,6 +172,41 @@ CREATE SCHEMA regress_schema_3 AUTHORIZATION CURRENT_ROLE
  regress_schema_3 | ss1  | regress_schema_3.ss |           |          | 'hello'::text | 
 (2 rows)
 
+-- Cases where the schema creation with collations.
+-- fail, cannot CREATE COLLATION to other schema
+CREATE SCHEMA regress_schema_4 AUTHORIZATION CURRENT_ROLE
+  CREATE COLLATION public.coll_icu_und FROM "und-x-icu";
+ERROR:  CREATE COLLATION specifies a schema (public) different from the one being created (regress_schema_4)
+-- fail, improper qualified name
+CREATE SCHEMA regress_schema_4 AUTHORIZATION CURRENT_ROLE
+  CREATE COLLATION postgres.public.coll_icu_und FROM "und-x-icu";
+ERROR:  cross-database references are not implemented: postgres.public.coll_icu_und
+-- fail, only support collation object for DefineStmt node
+CREATE SCHEMA regress_schema_4 AUTHORIZATION CURRENT_ROLE
+  CREATE AGGREGATE balk(int4) (SFUNC = int4_sum(int8, int4), STYPE = int8, PARALLEL = SAFE, INITCOND = '0');
+ERROR:  CREATE SCHEMA ... CREATE AGGREGATE currently not supported
+-- ok, qualified schema name for collation should be same as the created schema
+CREATE SCHEMA regress_schema_4 AUTHORIZATION CURRENT_ROLE
+  CREATE COLLATION regress_schema_4.coll (LOCALE="C")
+  CREATE TABLE t(a TEXT COLLATE regress_schema_4.coll);
+\dO regress_schema_4.*
+                                     List of collations
+      Schema      | Name | Provider | Collate | Ctype | Locale | ICU Rules | Deterministic? 
+------------------+------+----------+---------+-------+--------+-----------+----------------
+ regress_schema_4 | coll | libc     | C       | C     |        |           | yes
+(1 row)
+
+-- ok, no qualified schema name for collation
+CREATE SCHEMA regress_schema_5 AUTHORIZATION CURRENT_ROLE
+  CREATE COLLATION coll (LOCALE="C")
+  CREATE TABLE t(a TEXT COLLATE coll);
+\dO regress_schema_5.*
+                                     List of collations
+      Schema      | Name | Provider | Collate | Ctype | Locale | ICU Rules | Deterministic? 
+------------------+------+----------+---------+-------+--------+-----------+----------------
+ regress_schema_5 | coll | libc     | C       | C     |        |           | yes
+(1 row)
+
 DROP SCHEMA regress_schema_2 CASCADE;
 NOTICE:  drop cascades to 2 other objects
 DETAIL:  drop cascades to type regress_schema_2.ss
@@ -182,5 +217,13 @@ DETAIL:  drop cascades to type regress_schema_3.ss
 drop cascades to type regress_schema_3.ss1
 drop cascades to view regress_schema_3.test
 drop cascades to table regress_schema_3.t
+DROP SCHEMA regress_schema_4 CASCADE;
+NOTICE:  drop cascades to 2 other objects
+DETAIL:  drop cascades to collation regress_schema_4.coll
+drop cascades to table regress_schema_4.t
+DROP SCHEMA regress_schema_5 CASCADE;
+NOTICE:  drop cascades to 2 other objects
+DETAIL:  drop cascades to collation regress_schema_5.coll
+drop cascades to table regress_schema_5.t
 -- Clean up
 DROP ROLE regress_create_schema_role;
diff --git a/src/test/regress/sql/collate.icu.utf8.sql b/src/test/regress/sql/collate.icu.utf8.sql
index 8f0f973f5fa..782f46c48b7 100644
--- a/src/test/regress/sql/collate.icu.utf8.sql
+++ b/src/test/regress/sql/collate.icu.utf8.sql
@@ -517,6 +517,13 @@ CREATE COLLATION testcoll_rulesx (provider = icu, locale = '', rules = '!!wrong!
 CREATE COLLATION strength_in_rule (provider = icu, locale = 'und', deterministic = false, rules = '[strength 1]');
 SELECT 'a' = 'à' COLLATE strength_in_rule;  -- true because of the rule
 
+-- CREATE SCHEMA CREATE COLLATION
+CREATE SCHEMA regress_schema_4
+  CREATE COLLATION coll_icu_und FROM "und-x-icu"
+  CREATE TABLE tts(a TEXT COLLATE coll_icu_und);
+\dO regress_schema_4.*
+DROP SCHEMA regress_schema_4 CASCADE;
+
 
 -- nondeterministic collations
 
diff --git a/src/test/regress/sql/create_schema.sql b/src/test/regress/sql/create_schema.sql
index ec47b90c673..f7f28363b4a 100644
--- a/src/test/regress/sql/create_schema.sql
+++ b/src/test/regress/sql/create_schema.sql
@@ -99,8 +99,37 @@ CREATE SCHEMA regress_schema_3 AUTHORIZATION CURRENT_ROLE
   CREATE TABLE t(a ss1);
 \dD regress_schema_3.*
 
+-- Cases where the schema creation with collations.
+-- fail, cannot CREATE COLLATION to other schema
+CREATE SCHEMA regress_schema_4 AUTHORIZATION CURRENT_ROLE
+  CREATE COLLATION public.coll_icu_und FROM "und-x-icu";
+
+-- fail, improper qualified name
+CREATE SCHEMA regress_schema_4 AUTHORIZATION CURRENT_ROLE
+  CREATE COLLATION postgres.public.coll_icu_und FROM "und-x-icu";
+
+-- fail, only support collation object for DefineStmt node
+CREATE SCHEMA regress_schema_4 AUTHORIZATION CURRENT_ROLE
+  CREATE AGGREGATE balk(int4) (SFUNC = int4_sum(int8, int4), STYPE = int8, PARALLEL = SAFE, INITCOND = '0');
+
+-- ok, qualified schema name for collation should be same as the created schema
+CREATE SCHEMA regress_schema_4 AUTHORIZATION CURRENT_ROLE
+  CREATE COLLATION regress_schema_4.coll (LOCALE="C")
+  CREATE TABLE t(a TEXT COLLATE regress_schema_4.coll);
+
+\dO regress_schema_4.*
+
+-- ok, no qualified schema name for collation
+CREATE SCHEMA regress_schema_5 AUTHORIZATION CURRENT_ROLE
+  CREATE COLLATION coll (LOCALE="C")
+  CREATE TABLE t(a TEXT COLLATE coll);
+\dO regress_schema_5.*
+
+
 DROP SCHEMA regress_schema_2 CASCADE;
 DROP SCHEMA regress_schema_3 CASCADE;
+DROP SCHEMA regress_schema_4 CASCADE;
+DROP SCHEMA regress_schema_5 CASCADE;
 
 -- Clean up
 DROP ROLE regress_create_schema_role;
-- 
2.34.1



^ permalink  raw  reply  [nested|flat] 32+ messages in thread

* Re: CREATE SCHEMA ... CREATE DOMAIN support
  2024-11-30 19:08 Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  2024-12-03 19:35 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Peter Eisentraut <[email protected]>
  2024-12-03 20:07   ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  2024-12-11 06:22     ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <[email protected]>
  2024-12-11 17:08       ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  2025-08-19 03:36         ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <[email protected]>
  2025-08-22 08:59           ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <[email protected]>
  2025-09-02 21:24             ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  2025-12-17 13:29               ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <[email protected]>
@ 2026-04-03 18:35                 ` Tom Lane <[email protected]>
  2026-04-05 18:42                   ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  1 sibling, 1 reply; 32+ messages in thread

From: Tom Lane @ 2026-04-03 18:35 UTC (permalink / raw)
  To: jian he <[email protected]>; +Cc: Kirill Reshke <[email protected]>; Peter Eisentraut <[email protected]>; PostgreSQL Hackers <[email protected]>

I wrote [ many months ago now ]:
>> I think this is still kind of blocked, because it's not clear to me
>> whether we have consensus about it being okay to do 0001.
>> 
>> Re-reading the thread, the only real use-case for re-ordering that
>> anyone proposed is that foreign key references should be able to be
>> forward references to tables created later in the same CREATE SCHEMA.
>> I concede first that this is a somewhat-plausible use-case and
>> second that it is pretty clearly required by spec.  The fact remains
>> however that we have never supported that in two dozen years, and
>> the number of complaints about the omission could be counted without
>> running out of thumbs.  So, how about the following plan of action?
>> 
>> 1. Rip out subcommand re-ordering as currently implemented, and do the
>> subcommands in the given order.
>> 
>> 2. When a CREATE TABLE subcommand includes a FOREIGN KEY clause,
>> transform that clause into ALTER TABLE ADD FOREIGN KEY, and push
>> it to the back of the CREATE SCHEMA's to-do list.

Nobody has complained about this approach since September, so unless
there's a complaint PDQ, I'm going to take silence as consent and
move forward with reviewing/applying Jian's patchset.

			regards, tom lane





^ permalink  raw  reply  [nested|flat] 32+ messages in thread

* Re: CREATE SCHEMA ... CREATE DOMAIN support
  2024-11-30 19:08 Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  2024-12-03 19:35 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Peter Eisentraut <[email protected]>
  2024-12-03 20:07   ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  2024-12-11 06:22     ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <[email protected]>
  2024-12-11 17:08       ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  2025-08-19 03:36         ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <[email protected]>
  2025-08-22 08:59           ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <[email protected]>
  2025-09-02 21:24             ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  2025-12-17 13:29               ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <[email protected]>
  2026-04-03 18:35                 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
@ 2026-04-05 18:42                   ` Tom Lane <[email protected]>
  2026-04-05 20:15                     ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  0 siblings, 1 reply; 32+ messages in thread

From: Tom Lane @ 2026-04-05 18:42 UTC (permalink / raw)
  To: jian he <[email protected]>; +Cc: Kirill Reshke <[email protected]>; Peter Eisentraut <[email protected]>; PostgreSQL Hackers <[email protected]>

Apropos of the question of how much rearrangement functionality the
SQL spec actually asks for, I was interested to come across this entry
in SQL:2021's Table 43 — Feature taxonomy and definition for mandatory
features:

Feature ID	Feature Name	Feature Description

F311-01		CREATE SCHEMA	— Subclause 11.1, “<schema definition>”:
				Support for circular references in that <refer-
				ential constraint definition>s in two different
				<table definition>s may reference columns in
				the other table

We already claim support for F311-01 in sql_features.txt, but based
on this entry I'd have to say that's a lie.  However, (a) the current
patchset fixes that, and (b) this seems like good evidence in support
of the idea that circular foreign keys are the only aspect of the
business that anyone cares about.

			regards, tom lane





^ permalink  raw  reply  [nested|flat] 32+ messages in thread

* Re: CREATE SCHEMA ... CREATE DOMAIN support
  2024-11-30 19:08 Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  2024-12-03 19:35 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Peter Eisentraut <[email protected]>
  2024-12-03 20:07   ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  2024-12-11 06:22     ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <[email protected]>
  2024-12-11 17:08       ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  2025-08-19 03:36         ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <[email protected]>
  2025-08-22 08:59           ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <[email protected]>
  2025-09-02 21:24             ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  2025-12-17 13:29               ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <[email protected]>
  2026-04-03 18:35                 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  2026-04-05 18:42                   ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
@ 2026-04-05 20:15                     ` Tom Lane <[email protected]>
  2026-04-05 22:05                       ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  2026-04-06 02:29                       ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <[email protected]>
  0 siblings, 2 replies; 32+ messages in thread

From: Tom Lane @ 2026-04-05 20:15 UTC (permalink / raw)
  To: jian he <[email protected]>; +Cc: Kirill Reshke <[email protected]>; Peter Eisentraut <[email protected]>; PostgreSQL Hackers <[email protected]>

Here's a revised patchset that I think is pretty close to committable.
I reorganized it some compared to v11: 0001 is still about the same,
but the business with allowing circular foreign keys is now in 0002,
because it seems like that bears directly on whether we should
consider 0001 acceptable.  And then I squashed all the object-type
additions into 0003, because dealing with them all at once seemed
simpler.

I rewrote 0002 rather heavily, mainly because it assumed it could
scribble on the input query tree which I don't think is okay.
The functionality is still the same though.

I was kind of sad that 0003 didn't support functions/procedures,
because (a) the SQL spec requires those in CREATE SCHEMA, and
(b) they are a flagship feature for Postgres, mainly because they
are such a critical aspect of extendability.  So I added that,
which only required adding CreateFunctionStmt to the set of
supported node types in transformCreateSchemaStmtElements.

I really didn't like the stringify_objtype() business in 0003:
it's messy and there is no guarantee that ObjectTypeMap will map
ObjectType codes the way you want them.  However, we can easily
get rid of that.  It was used for reporting "CREATE <objecttype>"
in error messages, but we don't really need that in the wrong-
schema-name message, as evidenced by the precedent of checkSchemaName
which has always just said "CREATE specifies a schema ...".
And as for the other usage of rejecting subclasses of DefineStmt,
let's just not reject them.  The existing other subclasses are
CREATE AGGREGATE and CREATE OPERATOR, which I think we should
support if we are supporting functions, plus text search objects.
I don't especially care whether CREATE SCHEMA allows CREATE TEXT
SEARCH commands, but I certainly don't see the value of adding
grungy code just to reject them.

While testing this I observed that including new-style SQL
functions/procedures in CREATE SCHEMA failed, because psql's heuristic
for determining when a semicolon ends the command didn't account for
this case.  So 0003 includes a patch for that, but it's even more
heuristic-y than before; I don't know if there are any plausible cases
where it'd misbehave.

psql's tab-complete support could stand to be improved more.
I fixed the CREATE TEXT SEARCH rules to change Matches to TailMatches,
but there are other places such as CREATE COLLATION that I didn't
touch because they have usages of HeadMatches plus TailMatches, and
it's not clear how to make those rules work.  I don't think that's
a commit-blocker, though.

As noted in the draft commit message for 0003, this puts us at
a point where we could reasonably claim to be feature-complete
for CREATE SCHEMA.  We support all the subcommand types required
by the spec, except for object types we don't implement at all
yet, plus CREATE CAST/TRANSFORM/ROLE.  Regarding those three
types of object as belonging to a schema seems problematic
for us, because they don't have schema-qualified names.
So I'm content to draw a line under this and say we're done.
Perhaps we should rewrite create_schema.sgml's Compatibility
section with that in mind, and with less apology for not
supporting command re-ordering.

			regards, tom lane



Attachments:

  [text/x-diff] v12-0001-Don-t-try-to-re-order-the-subcommands-of-CREATE-.patch (26.5K, 2-v12-0001-Don-t-try-to-re-order-the-subcommands-of-CREATE-.patch)
  download | inline diff:
From c280586453b1dd29d64eb494178d50bc3c0f10e3 Mon Sep 17 00:00:00 2001
From: Tom Lane <[email protected]>
Date: Sat, 4 Apr 2026 21:18:41 -0400
Subject: [PATCH v12 1/3] Don't try to re-order the subcommands of CREATE
 SCHEMA.

transformCreateSchemaStmtElements has always believed that it is
supposed to re-order the subcommands of CREATE SCHEMA into a safe
execution order.  However, it is nowhere near being capable of doing
that correctly.  Nor is there reason to think that it ever will be,
or that that is a well-defined requirement.  (The SQL standard does
say that it should be possible to do foreign-key forward references
within CREATE SCHEMA, but it's not clear that the text requires
anything more than that.)  Moreover, the problem will get worse as
we add more subcommand types.  Let's just drop the whole idea and
execute the commands in the order given, which seems like a much
less astonishment-prone definition anyway.  The foreign-key issue
will be handled in a follow-up patch.

Along the way, pass down a ParseState so that we can provide an
error cursor for "wrong schema name" and related errors, and fix
transformCreateSchemaStmtElements so that it doesn't scribble
on the parsetree passed to it.

Author: Tom Lane <[email protected]>
Reviewed-by: Kirill Reshke <[email protected]>
Reviewed-by: jian he <[email protected]>
Discussion: https://postgr.es/m/[email protected]
---
 doc/src/sgml/ref/create_schema.sgml         |  12 +-
 src/backend/commands/extension.c            |   7 +-
 src/backend/commands/schemacmds.c           |  14 +--
 src/backend/parser/parse_utilcmd.c          | 131 ++++++++------------
 src/backend/tcop/utility.c                  |   4 +-
 src/include/commands/schemacmds.h           |   7 +-
 src/include/parser/parse_utilcmd.h          |   3 +-
 src/test/regress/expected/create_schema.out |  37 ++++++
 src/test/regress/expected/create_view.out   |   2 +
 src/test/regress/expected/event_trigger.out |   2 +-
 src/test/regress/expected/namespace.out     |   9 +-
 src/test/regress/sql/create_schema.sql      |   5 +
 src/test/regress/sql/namespace.sql          |  11 +-
 src/tools/pgindent/typedefs.list            |   1 -
 14 files changed, 134 insertions(+), 111 deletions(-)

diff --git a/doc/src/sgml/ref/create_schema.sgml b/doc/src/sgml/ref/create_schema.sgml
index ed69298ccc6..32367c71b25 100644
--- a/doc/src/sgml/ref/create_schema.sgml
+++ b/doc/src/sgml/ref/create_schema.sgml
@@ -194,11 +194,13 @@ CREATE VIEW hollywood.winners AS
 
   <para>
    The SQL standard specifies that the subcommands in <command>CREATE
-   SCHEMA</command> can appear in any order.  The present
-   <productname>PostgreSQL</productname> implementation does not
-   handle all cases of forward references in subcommands; it might
-   sometimes be necessary to reorder the subcommands in order to avoid
-   forward references.
+   SCHEMA</command> can appear in any order, and the implementation should
+   resolve forward references correctly.
+   <productname>PostgreSQL</productname> does not support that, because
+   it is unclear when an unqualified name should be resolved as an
+   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.
   </para>
 
   <para>
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index b98801d08f2..a330b5fd6ce 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1952,14 +1952,17 @@ CreateExtensionInternal(char *extensionName,
 
 		if (!OidIsValid(schemaOid))
 		{
+			ParseState *pstate = make_parsestate(NULL);
 			CreateSchemaStmt *csstmt = makeNode(CreateSchemaStmt);
 
+			pstate->p_sourcetext = "(generated CREATE SCHEMA command)";
+
 			csstmt->schemaname = schemaName;
 			csstmt->authrole = NULL;	/* will be created by current user */
 			csstmt->schemaElts = NIL;
 			csstmt->if_not_exists = false;
-			CreateSchemaCommand(csstmt, "(generated CREATE SCHEMA command)",
-								-1, -1);
+
+			CreateSchemaCommand(pstate, csstmt, -1, -1);
 
 			/*
 			 * CreateSchemaCommand includes CommandCounterIncrement, so new
diff --git a/src/backend/commands/schemacmds.c b/src/backend/commands/schemacmds.c
index 4c51e920626..bfaa4743cd8 100644
--- a/src/backend/commands/schemacmds.c
+++ b/src/backend/commands/schemacmds.c
@@ -49,7 +49,7 @@ static void AlterSchemaOwner_internal(HeapTuple tup, Relation rel, Oid newOwnerI
  * a subquery.
  */
 Oid
-CreateSchemaCommand(CreateSchemaStmt *stmt, const char *queryString,
+CreateSchemaCommand(ParseState *pstate, CreateSchemaStmt *stmt,
 					int stmt_location, int stmt_len)
 {
 	const char *schemaName = stmt->schemaname;
@@ -189,12 +189,12 @@ CreateSchemaCommand(CreateSchemaStmt *stmt, const char *queryString,
 
 	/*
 	 * Examine the list of commands embedded in the CREATE SCHEMA command, and
-	 * reorganize them into a sequentially executable order with no forward
-	 * references.  Note that the result is still a list of raw parsetrees ---
-	 * we cannot, in general, run parse analysis on one statement until we
-	 * have actually executed the prior ones.
+	 * do preliminary transformations.  Note that the result is still a list
+	 * of raw parsetrees --- we cannot, in general, run parse analysis on one
+	 * statement until we have actually executed the prior ones.
 	 */
-	parsetree_list = transformCreateSchemaStmtElements(stmt->schemaElts,
+	parsetree_list = transformCreateSchemaStmtElements(pstate,
+													   stmt->schemaElts,
 													   schemaName);
 
 	/*
@@ -219,7 +219,7 @@ CreateSchemaCommand(CreateSchemaStmt *stmt, const char *queryString,
 
 		/* do this step */
 		ProcessUtility(wrapper,
-					   queryString,
+					   pstate->p_sourcetext,
 					   false,
 					   PROCESS_UTILITY_SUBCOMMAND,
 					   NULL,
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 9a918e14aa7..cb2e57610b2 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -99,18 +99,6 @@ typedef struct
 	bool		ofType;			/* true if statement contains OF typename */
 } CreateStmtContext;
 
-/* State shared by transformCreateSchemaStmtElements and its subroutines */
-typedef struct
-{
-	const char *schemaname;		/* name of schema */
-	List	   *sequences;		/* CREATE SEQUENCE items */
-	List	   *tables;			/* CREATE TABLE items */
-	List	   *views;			/* CREATE VIEW items */
-	List	   *indexes;		/* CREATE INDEX items */
-	List	   *triggers;		/* CREATE TRIGGER items */
-	List	   *grants;			/* GRANT items */
-} CreateSchemaStmtContext;
-
 
 static void transformColumnDefinition(CreateStmtContext *cxt,
 									  ColumnDef *column);
@@ -137,7 +125,8 @@ static void transformCheckConstraints(CreateStmtContext *cxt,
 static void transformConstraintAttrs(CreateStmtContext *cxt,
 									 List *constraintList);
 static void transformColumnType(CreateStmtContext *cxt, ColumnDef *column);
-static void setSchemaName(const char *context_schema, char **stmt_schema_name);
+static void checkSchemaName(ParseState *pstate, const char *context_schema,
+							RangeVar *relation);
 static void transformPartitionCmd(CreateStmtContext *cxt, PartitionBoundSpec *bound);
 static List *transformPartitionRangeBounds(ParseState *pstate, List *blist,
 										   Relation parent);
@@ -4395,51 +4384,35 @@ transformColumnType(CreateStmtContext *cxt, ColumnDef *column)
  * transformCreateSchemaStmtElements -
  *	  analyzes the elements of a CREATE SCHEMA statement
  *
- * Split the schema element list from a CREATE SCHEMA statement into
- * individual commands and place them in the result list in an order
- * such that there are no forward references (e.g. GRANT to a table
- * created later in the list). Note that the logic we use for determining
- * forward references is presently quite incomplete.
+ * 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.
+ * 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.
  *
  * "schemaName" is the name of the schema that will be used for the creation
- * of the objects listed, that may be compiled from the schema name defined
+ * of the objects listed.  It may be obtained from the schema name defined
  * in the statement or a role specification.
  *
- * SQL also allows constraints to make forward references, so thumb through
- * the table columns and move forward references to a posterior alter-table
- * command.
- *
  * 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: this breaks the rules a little bit by modifying schema-name fields
- * within passed-in structs.  However, the transformation would be the same
- * if done over, so it should be all right to scribble on the input to this
- * extent.
  */
 List *
-transformCreateSchemaStmtElements(List *schemaElts, const char *schemaName)
+transformCreateSchemaStmtElements(ParseState *pstate, List *schemaElts,
+								  const char *schemaName)
 {
-	CreateSchemaStmtContext cxt;
-	List	   *result;
-	ListCell   *elements;
-
-	cxt.schemaname = schemaName;
-	cxt.sequences = NIL;
-	cxt.tables = NIL;
-	cxt.views = NIL;
-	cxt.indexes = NIL;
-	cxt.triggers = NIL;
-	cxt.grants = NIL;
+	List	   *elements = NIL;
+	ListCell   *lc;
 
 	/*
-	 * Run through each schema element in the schema element list. Separate
-	 * statements by type, and do preliminary analysis.
+	 * Run through each schema element in the schema element list.  Check
+	 * target schema names, and collect the list of actions to be done.
 	 */
-	foreach(elements, schemaElts)
+	foreach(lc, schemaElts)
 	{
-		Node	   *element = lfirst(elements);
+		Node	   *element = lfirst(lc);
 
 		switch (nodeTag(element))
 		{
@@ -4447,8 +4420,8 @@ transformCreateSchemaStmtElements(List *schemaElts, const char *schemaName)
 				{
 					CreateSeqStmt *elp = (CreateSeqStmt *) element;
 
-					setSchemaName(cxt.schemaname, &elp->sequence->schemaname);
-					cxt.sequences = lappend(cxt.sequences, element);
+					checkSchemaName(pstate, schemaName, elp->sequence);
+					elements = lappend(elements, element);
 				}
 				break;
 
@@ -4456,12 +4429,8 @@ transformCreateSchemaStmtElements(List *schemaElts, const char *schemaName)
 				{
 					CreateStmt *elp = (CreateStmt *) element;
 
-					setSchemaName(cxt.schemaname, &elp->relation->schemaname);
-
-					/*
-					 * XXX todo: deal with constraints
-					 */
-					cxt.tables = lappend(cxt.tables, element);
+					checkSchemaName(pstate, schemaName, elp->relation);
+					elements = lappend(elements, element);
 				}
 				break;
 
@@ -4469,12 +4438,8 @@ transformCreateSchemaStmtElements(List *schemaElts, const char *schemaName)
 				{
 					ViewStmt   *elp = (ViewStmt *) element;
 
-					setSchemaName(cxt.schemaname, &elp->view->schemaname);
-
-					/*
-					 * XXX todo: deal with references between views
-					 */
-					cxt.views = lappend(cxt.views, element);
+					checkSchemaName(pstate, schemaName, elp->view);
+					elements = lappend(elements, element);
 				}
 				break;
 
@@ -4482,8 +4447,8 @@ transformCreateSchemaStmtElements(List *schemaElts, const char *schemaName)
 				{
 					IndexStmt  *elp = (IndexStmt *) element;
 
-					setSchemaName(cxt.schemaname, &elp->relation->schemaname);
-					cxt.indexes = lappend(cxt.indexes, element);
+					checkSchemaName(pstate, schemaName, elp->relation);
+					elements = lappend(elements, element);
 				}
 				break;
 
@@ -4491,13 +4456,13 @@ transformCreateSchemaStmtElements(List *schemaElts, const char *schemaName)
 				{
 					CreateTrigStmt *elp = (CreateTrigStmt *) element;
 
-					setSchemaName(cxt.schemaname, &elp->relation->schemaname);
-					cxt.triggers = lappend(cxt.triggers, element);
+					checkSchemaName(pstate, schemaName, elp->relation);
+					elements = lappend(elements, element);
 				}
 				break;
 
 			case T_GrantStmt:
-				cxt.grants = lappend(cxt.grants, element);
+				elements = lappend(elements, element);
 				break;
 
 			default:
@@ -4506,32 +4471,40 @@ transformCreateSchemaStmtElements(List *schemaElts, const char *schemaName)
 		}
 	}
 
-	result = NIL;
-	result = list_concat(result, cxt.sequences);
-	result = list_concat(result, cxt.tables);
-	result = list_concat(result, cxt.views);
-	result = list_concat(result, cxt.indexes);
-	result = list_concat(result, cxt.triggers);
-	result = list_concat(result, cxt.grants);
-
-	return result;
+	return elements;
 }
 
 /*
- * setSchemaName
- *		Set or check schema name in an element of a CREATE SCHEMA command
+ * checkSchemaName
+ *		Check schema name in an element of a CREATE SCHEMA command
+ *
+ * It's okay if the command doesn't specify a target schema name, because
+ * CreateSchemaCommand will set up the default creation schema to be the
+ * new schema.  But if a target schema name is given, it had better match.
+ * We also have to check that the command doesn't say CREATE TEMP, since
+ * that would likewise put the object into the wrong schema.
  */
 static void
-setSchemaName(const char *context_schema, char **stmt_schema_name)
+checkSchemaName(ParseState *pstate, const char *context_schema,
+				RangeVar *relation)
 {
-	if (*stmt_schema_name == NULL)
-		*stmt_schema_name = unconstify(char *, context_schema);
-	else if (strcmp(context_schema, *stmt_schema_name) != 0)
+	if (relation->schemaname != NULL &&
+		strcmp(context_schema, relation->schemaname) != 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_SCHEMA_DEFINITION),
 				 errmsg("CREATE specifies a schema (%s) "
 						"different from the one being created (%s)",
-						*stmt_schema_name, context_schema)));
+						relation->schemaname, context_schema),
+				 parser_errposition(pstate, relation->location)));
+
+	if (relation->relpersistence == RELPERSISTENCE_TEMP)
+	{
+		/* spell this error the same as in RangeVarAdjustRelationPersistence */
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+				 errmsg("cannot create temporary relation in non-temporary schema"),
+				 parser_errposition(pstate, relation->location)));
+	}
 }
 
 /*
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 2b609bfc824..832d7647904 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1122,8 +1122,8 @@ ProcessUtilitySlow(ParseState *pstate,
 				 * relation and attribute manipulation
 				 */
 			case T_CreateSchemaStmt:
-				CreateSchemaCommand((CreateSchemaStmt *) parsetree,
-									queryString,
+				CreateSchemaCommand(pstate,
+									(CreateSchemaStmt *) parsetree,
 									pstmt->stmt_location,
 									pstmt->stmt_len);
 
diff --git a/src/include/commands/schemacmds.h b/src/include/commands/schemacmds.h
index d100e4691b3..b4348436dd3 100644
--- a/src/include/commands/schemacmds.h
+++ b/src/include/commands/schemacmds.h
@@ -16,12 +16,11 @@
 #define SCHEMACMDS_H
 
 #include "catalog/objectaddress.h"
-#include "nodes/parsenodes.h"
+#include "parser/parse_node.h"
 
-extern Oid	CreateSchemaCommand(CreateSchemaStmt *stmt,
-								const char *queryString,
+extern Oid	CreateSchemaCommand(ParseState *pstate,
+								CreateSchemaStmt *stmt,
 								int stmt_location, int stmt_len);
-
 extern ObjectAddress RenameSchema(const char *oldname, const char *newname);
 extern ObjectAddress AlterSchemaOwner(const char *name, Oid newOwnerId);
 extern void AlterSchemaOwner_oid(Oid schemaoid, Oid newOwnerId);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 53f282f77ec..34c98e5122f 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -30,7 +30,8 @@ extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-extern List *transformCreateSchemaStmtElements(List *schemaElts,
+extern List *transformCreateSchemaStmtElements(ParseState *pstate,
+											   List *schemaElts,
 											   const char *schemaName);
 extern PartitionBoundSpec *transformPartitionBound(ParseState *pstate, Relation parent,
 												   PartitionBoundSpec *spec);
diff --git a/src/test/regress/expected/create_schema.out b/src/test/regress/expected/create_schema.out
index 93302a07efc..4ab947a60a8 100644
--- a/src/test/regress/expected/create_schema.out
+++ b/src/test/regress/expected/create_schema.out
@@ -9,55 +9,92 @@ CREATE ROLE regress_create_schema_role SUPERUSER;
 CREATE SCHEMA AUTHORIZATION regress_create_schema_role
   CREATE SEQUENCE schema_not_existing.seq;
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_create_schema_role)
+LINE 2:   CREATE SEQUENCE schema_not_existing.seq;
+                          ^
 CREATE SCHEMA AUTHORIZATION regress_create_schema_role
   CREATE TABLE schema_not_existing.tab (id int);
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_create_schema_role)
+LINE 2:   CREATE TABLE schema_not_existing.tab (id int);
+                       ^
 CREATE SCHEMA AUTHORIZATION regress_create_schema_role
   CREATE VIEW schema_not_existing.view AS SELECT 1;
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_create_schema_role)
+LINE 2:   CREATE VIEW schema_not_existing.view AS SELECT 1;
+                      ^
 CREATE SCHEMA AUTHORIZATION regress_create_schema_role
   CREATE INDEX ON schema_not_existing.tab (id);
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_create_schema_role)
+LINE 2:   CREATE INDEX ON schema_not_existing.tab (id);
+                          ^
 CREATE SCHEMA AUTHORIZATION regress_create_schema_role
   CREATE TRIGGER schema_trig BEFORE INSERT ON schema_not_existing.tab
   EXECUTE FUNCTION schema_trig.no_func();
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_create_schema_role)
+LINE 2:   CREATE TRIGGER schema_trig BEFORE INSERT ON schema_not_exi...
+                                                      ^
 -- Again, with a role specification and no schema names.
 SET ROLE regress_create_schema_role;
 CREATE SCHEMA AUTHORIZATION CURRENT_ROLE
   CREATE SEQUENCE schema_not_existing.seq;
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_create_schema_role)
+LINE 2:   CREATE SEQUENCE schema_not_existing.seq;
+                          ^
 CREATE SCHEMA AUTHORIZATION CURRENT_ROLE
   CREATE TABLE schema_not_existing.tab (id int);
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_create_schema_role)
+LINE 2:   CREATE TABLE schema_not_existing.tab (id int);
+                       ^
 CREATE SCHEMA AUTHORIZATION CURRENT_ROLE
   CREATE VIEW schema_not_existing.view AS SELECT 1;
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_create_schema_role)
+LINE 2:   CREATE VIEW schema_not_existing.view AS SELECT 1;
+                      ^
 CREATE SCHEMA AUTHORIZATION CURRENT_ROLE
   CREATE INDEX ON schema_not_existing.tab (id);
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_create_schema_role)
+LINE 2:   CREATE INDEX ON schema_not_existing.tab (id);
+                          ^
 CREATE SCHEMA AUTHORIZATION CURRENT_ROLE
   CREATE TRIGGER schema_trig BEFORE INSERT ON schema_not_existing.tab
   EXECUTE FUNCTION schema_trig.no_func();
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_create_schema_role)
+LINE 2:   CREATE TRIGGER schema_trig BEFORE INSERT ON schema_not_exi...
+                                                      ^
 -- Again, with a schema name and a role specification.
 CREATE SCHEMA regress_schema_1 AUTHORIZATION CURRENT_ROLE
   CREATE SEQUENCE schema_not_existing.seq;
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_schema_1)
+LINE 2:   CREATE SEQUENCE schema_not_existing.seq;
+                          ^
 CREATE SCHEMA regress_schema_1 AUTHORIZATION CURRENT_ROLE
   CREATE TABLE schema_not_existing.tab (id int);
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_schema_1)
+LINE 2:   CREATE TABLE schema_not_existing.tab (id int);
+                       ^
 CREATE SCHEMA regress_schema_1 AUTHORIZATION CURRENT_ROLE
   CREATE VIEW schema_not_existing.view AS SELECT 1;
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_schema_1)
+LINE 2:   CREATE VIEW schema_not_existing.view AS SELECT 1;
+                      ^
 CREATE SCHEMA regress_schema_1 AUTHORIZATION CURRENT_ROLE
   CREATE INDEX ON schema_not_existing.tab (id);
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_schema_1)
+LINE 2:   CREATE INDEX ON schema_not_existing.tab (id);
+                          ^
 CREATE SCHEMA regress_schema_1 AUTHORIZATION CURRENT_ROLE
   CREATE TRIGGER schema_trig BEFORE INSERT ON schema_not_existing.tab
   EXECUTE FUNCTION schema_trig.no_func();
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_schema_1)
+LINE 2:   CREATE TRIGGER schema_trig BEFORE INSERT ON schema_not_exi...
+                                                      ^
 RESET ROLE;
+-- Forward references no longer work in general.
+CREATE SCHEMA regress_schema_1 AUTHORIZATION CURRENT_ROLE
+  CREATE VIEW abcd_view AS SELECT a FROM abcd
+  CREATE TABLE abcd (a int);
+ERROR:  relation "abcd" does not exist
+LINE 2:   CREATE VIEW abcd_view AS SELECT a FROM abcd
+                                                 ^
 -- Cases where the schema creation succeeds.
 -- The schema created matches the role name.
 CREATE SCHEMA AUTHORIZATION regress_create_schema_role
diff --git a/src/test/regress/expected/create_view.out b/src/test/regress/expected/create_view.out
index bc4f79938b3..63cf4b4371d 100644
--- a/src/test/regress/expected/create_view.out
+++ b/src/test/regress/expected/create_view.out
@@ -128,6 +128,8 @@ ERROR:  cannot create temporary relation in non-temporary schema
 CREATE SCHEMA test_view_schema
     CREATE TEMP VIEW testview AS SELECT 1;
 ERROR:  cannot create temporary relation in non-temporary schema
+LINE 2:     CREATE TEMP VIEW testview AS SELECT 1;
+                             ^
 -- joins: if any of the join relations are temporary, the view
 -- should also be temporary
 -- should be non-temp
diff --git a/src/test/regress/expected/event_trigger.out b/src/test/regress/expected/event_trigger.out
index f897b079e67..4c32e1dcaf5 100644
--- a/src/test/regress/expected/event_trigger.out
+++ b/src/test/regress/expected/event_trigger.out
@@ -425,13 +425,13 @@ NOTICE:  END: command_tag=CREATE TABLE type=table identity=evttrig.one
 NOTICE:  END: command_tag=CREATE INDEX type=index identity=evttrig.one_pkey
 NOTICE:  END: command_tag=ALTER SEQUENCE type=sequence identity=evttrig.one_col_a_seq
 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=CREATE INDEX type=index identity=evttrig.one_idx
 -- 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/expected/namespace.out b/src/test/regress/expected/namespace.out
index dbbda72d395..2e582e783c2 100644
--- a/src/test/regress/expected/namespace.out
+++ b/src/test/regress/expected/namespace.out
@@ -10,13 +10,14 @@ SELECT pg_catalog.set_config('search_path', ' ', false);
 (1 row)
 
 CREATE SCHEMA test_ns_schema_1
-       CREATE UNIQUE INDEX abc_a_idx ON abc (a)
-       CREATE VIEW abc_view AS
-              SELECT a+1 AS a, b+1 AS b FROM abc
        CREATE TABLE abc (
               a serial,
               b int UNIQUE
-       );
+       )
+       CREATE UNIQUE INDEX abc_a_idx ON abc (a)
+       CREATE VIEW abc_view AS
+              SELECT a+1 AS a, b+1 AS b FROM abc
+;
 -- verify that the correct search_path restored on abort
 SET search_path to public;
 BEGIN;
diff --git a/src/test/regress/sql/create_schema.sql b/src/test/regress/sql/create_schema.sql
index 1b7064247a1..62651342114 100644
--- a/src/test/regress/sql/create_schema.sql
+++ b/src/test/regress/sql/create_schema.sql
@@ -47,6 +47,11 @@ CREATE SCHEMA regress_schema_1 AUTHORIZATION CURRENT_ROLE
   EXECUTE FUNCTION schema_trig.no_func();
 RESET ROLE;
 
+-- Forward references no longer work in general.
+CREATE SCHEMA regress_schema_1 AUTHORIZATION CURRENT_ROLE
+  CREATE VIEW abcd_view AS SELECT a FROM abcd
+  CREATE TABLE abcd (a int);
+
 -- Cases where the schema creation succeeds.
 -- The schema created matches the role name.
 CREATE SCHEMA AUTHORIZATION regress_create_schema_role
diff --git a/src/test/regress/sql/namespace.sql b/src/test/regress/sql/namespace.sql
index 306cdc2d8c6..a75d4f580d3 100644
--- a/src/test/regress/sql/namespace.sql
+++ b/src/test/regress/sql/namespace.sql
@@ -7,15 +7,16 @@
 SELECT pg_catalog.set_config('search_path', ' ', false);
 
 CREATE SCHEMA test_ns_schema_1
+       CREATE TABLE abc (
+              a serial,
+              b int UNIQUE
+       )
+
        CREATE UNIQUE INDEX abc_a_idx ON abc (a)
 
        CREATE VIEW abc_view AS
               SELECT a+1 AS a, b+1 AS b FROM abc
-
-       CREATE TABLE abc (
-              a serial,
-              b int UNIQUE
-       );
+;
 
 -- verify that the correct search_path restored on abort
 SET search_path to public;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index c72f6c59573..7b0cb83774a 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -585,7 +585,6 @@ CreateRangeStmt
 CreateReplicationSlotCmd
 CreateRoleStmt
 CreateSchemaStmt
-CreateSchemaStmtContext
 CreateSeqStmt
 CreateStatsStmt
 CreateStmt
-- 
2.43.7



  [text/x-diff] v12-0002-Execute-foreign-key-constraints-in-CREATE-SCHEMA.patch (24.4K, 3-v12-0002-Execute-foreign-key-constraints-in-CREATE-SCHEMA.patch)
  download | inline diff:
From d0abc5e8fb7392623a6826de7d6abc4b2ac65c0d Mon Sep 17 00:00:00 2001
From: Tom Lane <[email protected]>
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 <[email protected]>
Reviewed-by: Tom Lane <[email protected]>
Discussion: https://postgr.es/m/[email protected]
---
 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



  [text/x-diff] v12-0003-Support-more-object-types-within-CREATE-SCHEMA.patch (25.7K, 4-v12-0003-Support-more-object-types-within-CREATE-SCHEMA.patch)
  download | inline diff:
From bc842fe2819a4334e51a2188d27f9c8dd1655ee2 Mon Sep 17 00:00:00 2001
From: Tom Lane <[email protected]>
Date: Sun, 5 Apr 2026 15:37:21 -0400
Subject: [PATCH v12 3/3] Support more object types within CREATE SCHEMA.

Having rejected the principle that we should know how to re-order
the sub-commands of CREATE SCHEMA, there is not really anything
except a little coding to stop us from supporting more object types.
This patch adds support for creating functions (including procedures
and aggregates), operators, types (including domains), collations,
and text search objects.

SQL:2021 specifies that we should allow functions, procedures,
types, domains, and collations, so this moves us a great deal
closer to full SQL compatibility of CREATE SCHEMA.  What remains
missing from their list are object types we don't have at all
(e.g. CREATE CHARACTER SET), casts, transforms, and roles.
Supporting casts or transforms would be problematic because
they don't have names at all, let alone schema-qualified names,
so it'd be quite a stretch to say that they belong to a schema.
Roles likewise are not schema-qualified, plus they are global
to a cluster, making it even less reasonable to consider them
as belonging to a schema.  So I don't see us trying to complete
the list.

User-defined aggregates and operators are outside the spec's ken,
as are text search objects, so adding them does not do anything for
spec compatibility.  But they go along with these other object types,
plus it takes no additional code to support them since they are
represented as DefineStmts like some variants of CREATE TYPE.
It would indeed take some effort to reject them.

Author: Kirill Reshke <[email protected]>
Author: Jian He <[email protected]>
Reviewed-by: Tom Lane <[email protected]>
Discussion: https://postgr.es/m/CALdSSPh4jUSDsWu3K58hjO60wnTRR0DuO4CKRcwa8EVuOSfXxg@mail.gmail.com
---
 doc/src/sgml/ref/create_schema.sgml           |  25 +++-
 src/backend/parser/gram.y                     |   3 +
 src/backend/parser/parse_utilcmd.c            |  91 +++++++++++-
 src/bin/psql/tab-complete.in.c                |  22 +--
 src/fe_utils/psqlscan.l                       |  22 +--
 .../expected/create_schema.out                |  38 ++++-
 .../test_ddl_deparse/sql/create_schema.sql    |  18 ++-
 src/test/regress/expected/create_schema.out   | 130 ++++++++++++++++++
 src/test/regress/sql/create_schema.sql        |  67 +++++++++
 9 files changed, 391 insertions(+), 25 deletions(-)

diff --git a/doc/src/sgml/ref/create_schema.sgml b/doc/src/sgml/ref/create_schema.sgml
index 8e3c61890b8..4fe1b77f0ef 100644
--- a/doc/src/sgml/ref/create_schema.sgml
+++ b/doc/src/sgml/ref/create_schema.sgml
@@ -100,12 +100,27 @@ CREATE SCHEMA IF NOT EXISTS AUTHORIZATION <replaceable class="parameter">role_sp
       <listitem>
        <para>
         An SQL statement defining an object to be created within the
-        schema. Currently, only <command>CREATE
-        TABLE</command>, <command>CREATE VIEW</command>, <command>CREATE
-        INDEX</command>, <command>CREATE SEQUENCE</command>, <command>CREATE
-        TRIGGER</command> and <command>GRANT</command> are accepted as clauses
+        schema. Currently, only
+        <command>CREATE AGGREGATE</command>,
+        <command>CREATE COLLATION</command>,
+        <command>CREATE DOMAIN</command>,
+        <command>CREATE FUNCTION</command>,
+        <command>CREATE INDEX</command>,
+        <command>CREATE OPERATOR</command>,
+        <command>CREATE PROCEDURE</command>,
+        <command>CREATE SEQUENCE</command>,
+        <command>CREATE TABLE</command>,
+        <command>CREATE TEXT SEARCH CONFIGURATION</command>,
+        <command>CREATE TEXT SEARCH DICTIONARY</command>,
+        <command>CREATE TEXT SEARCH PARSER</command>,
+        <command>CREATE TEXT SEARCH TEMPLATE</command>,
+        <command>CREATE TRIGGER</command>,
+        <command>CREATE TYPE</command>,
+        <command>CREATE VIEW</command>,
+        and <command>GRANT</command> are accepted as clauses
         within <command>CREATE SCHEMA</command>. Other kinds of objects may
-        be created in separate commands after the schema is created.
+        be created within the schema in separate commands after the schema
+        is created.
        </para>
       </listitem>
      </varlistentry>
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index f4a08baa95a..2d1b19d1f53 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -1679,8 +1679,11 @@ OptSchemaEltList:
 schema_stmt:
 			CreateStmt
 			| IndexStmt
+			| CreateDomainStmt
+			| CreateFunctionStmt
 			| CreateSeqStmt
 			| CreateTrigStmt
+			| DefineStmt
 			| GrantStmt
 			| ViewStmt
 		;
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 1bc5df7e0ef..9348e594dab 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -127,6 +127,8 @@ static void transformConstraintAttrs(ParseState *pstate,
 static void transformColumnType(CreateStmtContext *cxt, ColumnDef *column);
 static void checkSchemaName(ParseState *pstate, const char *context_schema,
 							RangeVar *relation);
+static void checkSchemaNameList(const char *context_schema,
+								List *qualified_name);
 static CreateStmt *transformCreateSchemaCreateTable(ParseState *pstate,
 													CreateStmt *stmt,
 													List **fk_elements);
@@ -4481,6 +4483,68 @@ transformCreateSchemaStmtElements(ParseState *pstate, List *schemaElts,
 				}
 				break;
 
+			case T_CreateDomainStmt:
+				{
+					CreateDomainStmt *elp = (CreateDomainStmt *) element;
+
+					checkSchemaNameList(schemaName, elp->domainname);
+					elements = lappend(elements, element);
+				}
+				break;
+
+			case T_CreateFunctionStmt:
+				{
+					CreateFunctionStmt *elp = (CreateFunctionStmt *) element;
+
+					checkSchemaNameList(schemaName, elp->funcname);
+					elements = lappend(elements, element);
+				}
+				break;
+
+				/*
+				 * CREATE TYPE can produce a DefineStmt, but also
+				 * CreateEnumStmt, CreateRangeStmt, and CompositeTypeStmt.
+				 * Allowing DefineStmt also provides support for several other
+				 * commands: currently, CREATE AGGREGATE, CREATE COLLATION,
+				 * CREATE OPERATOR, and text search objects.
+				 */
+
+			case T_DefineStmt:
+				{
+					DefineStmt *elp = (DefineStmt *) element;
+
+					checkSchemaNameList(schemaName, elp->defnames);
+					elements = lappend(elements, element);
+				}
+				break;
+
+			case T_CreateEnumStmt:
+				{
+					CreateEnumStmt *elp = (CreateEnumStmt *) element;
+
+					checkSchemaNameList(schemaName, elp->typeName);
+					elements = lappend(elements, element);
+				}
+				break;
+
+			case T_CreateRangeStmt:
+				{
+					CreateRangeStmt *elp = (CreateRangeStmt *) element;
+
+					checkSchemaNameList(schemaName, elp->typeName);
+					elements = lappend(elements, element);
+				}
+				break;
+
+			case T_CompositeTypeStmt:
+				{
+					CompositeTypeStmt *elp = (CompositeTypeStmt *) element;
+
+					checkSchemaName(pstate, schemaName, elp->typevar);
+					elements = lappend(elements, element);
+				}
+				break;
+
 			case T_GrantStmt:
 				elements = lappend(elements, element);
 				break;
@@ -4496,7 +4560,8 @@ transformCreateSchemaStmtElements(ParseState *pstate, List *schemaElts,
 
 /*
  * checkSchemaName
- *		Check schema name in an element of a CREATE SCHEMA command
+ *		Check schema name in an element of a CREATE SCHEMA command,
+ *		where the element's name is given by a RangeVar
  *
  * It's okay if the command doesn't specify a target schema name, because
  * CreateSchemaCommand will set up the default creation schema to be the
@@ -4527,6 +4592,30 @@ checkSchemaName(ParseState *pstate, const char *context_schema,
 	}
 }
 
+/*
+ * checkSchemaNameList
+ *		Check schema name in an element of a CREATE SCHEMA command,
+ *		where the element's name is given by a List
+ *
+ * Much as above, but we don't have to worry about TEMP.
+ * Sadly, this also means we don't have a parse location to report.
+ */
+static void
+checkSchemaNameList(const char *context_schema, List *qualified_name)
+{
+	char	   *obj_schema;
+	char	   *obj_name;
+
+	DeconstructQualifiedName(qualified_name, &obj_schema, &obj_name);
+	if (obj_schema != NULL &&
+		strcmp(context_schema, obj_schema) != 0)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_SCHEMA_DEFINITION),
+				 errmsg("CREATE specifies a schema (%s) "
+						"different from the one being created (%s)",
+						obj_schema, context_schema)));
+}
+
 /*
  * transformCreateSchemaCreateTable
  *		Process one CreateStmt for transformCreateSchemaStmtElements.
diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c
index 53bf1e21721..76d7edd4619 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -2210,7 +2210,11 @@ match_previous_words(int pattern_id,
 	{
 		/* only some object types can be created as part of CREATE SCHEMA */
 		if (HeadMatches("CREATE", "SCHEMA"))
-			COMPLETE_WITH("TABLE", "VIEW", "INDEX", "SEQUENCE", "TRIGGER",
+			COMPLETE_WITH("AGGREGATE", "COLLATION", "DOMAIN", "FUNCTION",
+						  "INDEX", "OPERATOR", "PROCEDURE", "SEQUENCE", "TABLE",
+						  "TEXT SEARCH CONFIGURATION", "TEXT SEARCH DICTIONARY",
+						  "TEXT SEARCH PARSER", "TEXT SEARCH TEMPLATE",
+						  "TRIGGER", "TYPE", "VIEW",
 			/* for INDEX and TABLE/SEQUENCE, respectively */
 						  "UNIQUE", "UNLOGGED");
 		else
@@ -3534,15 +3538,15 @@ match_previous_words(int pattern_id,
 	else if (Matches("CREATE", "DATABASE", MatchAny, "STRATEGY"))
 		COMPLETE_WITH("WAL_LOG", "FILE_COPY");
 
-	/* CREATE DOMAIN */
-	else if (Matches("CREATE", "DOMAIN", MatchAny))
+	/* CREATE DOMAIN --- is allowed inside CREATE SCHEMA, so use TailMatches */
+	else if (TailMatches("CREATE", "DOMAIN", MatchAny))
 		COMPLETE_WITH("AS");
-	else if (Matches("CREATE", "DOMAIN", MatchAny, "AS"))
+	else if (TailMatches("CREATE", "DOMAIN", MatchAny, "AS"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes);
-	else if (Matches("CREATE", "DOMAIN", MatchAny, "AS", MatchAny))
+	else if (TailMatches("CREATE", "DOMAIN", MatchAny, "AS", MatchAny))
 		COMPLETE_WITH("COLLATE", "DEFAULT", "CONSTRAINT",
 					  "NOT NULL", "NULL", "CHECK (");
-	else if (Matches("CREATE", "DOMAIN", MatchAny, "COLLATE"))
+	else if (TailMatches("CREATE", "DOMAIN", MatchAny, "COLLATE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_collations);
 
 	/* CREATE EXTENSION */
@@ -3906,10 +3910,10 @@ match_previous_words(int pattern_id,
 	else if (Matches("CREATE", "TABLESPACE", MatchAny, "OWNER", MatchAny))
 		COMPLETE_WITH("LOCATION");
 
-/* CREATE TEXT SEARCH */
-	else if (Matches("CREATE", "TEXT", "SEARCH"))
+/* CREATE TEXT SEARCH --- is allowed inside CREATE SCHEMA, so use TailMatches */
+	else if (TailMatches("CREATE", "TEXT", "SEARCH"))
 		COMPLETE_WITH("CONFIGURATION", "DICTIONARY", "PARSER", "TEMPLATE");
-	else if (Matches("CREATE", "TEXT", "SEARCH", "CONFIGURATION|DICTIONARY|PARSER|TEMPLATE", MatchAny))
+	else if (TailMatches("CREATE", "TEXT", "SEARCH", "CONFIGURATION|DICTIONARY|PARSER|TEMPLATE", MatchAny))
 		COMPLETE_WITH("(");
 
 /* CREATE TRANSFORM */
diff --git a/src/fe_utils/psqlscan.l b/src/fe_utils/psqlscan.l
index 7e1f9b22c49..d29dda4d8e1 100644
--- a/src/fe_utils/psqlscan.l
+++ b/src/fe_utils/psqlscan.l
@@ -928,19 +928,24 @@ other			.
 					 * writing a full parser here, the following heuristic
 					 * should work.  First, we track whether the beginning of
 					 * the statement matches CREATE [OR REPLACE]
-					 * {FUNCTION|PROCEDURE}
+					 * {FUNCTION|PROCEDURE|SCHEMA}.  (Allowing this in
+					 * CREATE SCHEMA, without tracking whether we're within a
+					 * CREATE FUNCTION/PROCEDURE subcommand, is a bit shaky
+					 * but should be okay with the present set of valid
+					 * subcommands.)
 					 */
 
 					if (cur_state->identifier_count == 0)
 						memset(cur_state->identifiers, 0, sizeof(cur_state->identifiers));
 
-					if (pg_strcasecmp(yytext, "create") == 0 ||
-						pg_strcasecmp(yytext, "function") == 0 ||
-						pg_strcasecmp(yytext, "procedure") == 0 ||
-						pg_strcasecmp(yytext, "or") == 0 ||
-						pg_strcasecmp(yytext, "replace") == 0)
+					if (cur_state->identifier_count < sizeof(cur_state->identifiers))
 					{
-						if (cur_state->identifier_count < sizeof(cur_state->identifiers))
+						if (pg_strcasecmp(yytext, "create") == 0 ||
+							pg_strcasecmp(yytext, "function") == 0 ||
+							pg_strcasecmp(yytext, "procedure") == 0 ||
+							pg_strcasecmp(yytext, "or") == 0 ||
+							pg_strcasecmp(yytext, "replace") == 0 ||
+							pg_strcasecmp(yytext, "schema") == 0)
 							cur_state->identifiers[cur_state->identifier_count] = pg_tolower((unsigned char) yytext[0]);
 					}
 
@@ -949,7 +954,8 @@ other			.
 					if (cur_state->identifiers[0] == 'c' &&
 						(cur_state->identifiers[1] == 'f' || cur_state->identifiers[1] == 'p' ||
 						 (cur_state->identifiers[1] == 'o' && cur_state->identifiers[2] == 'r' &&
-						  (cur_state->identifiers[3] == 'f' || cur_state->identifiers[3] == 'p'))) &&
+						  (cur_state->identifiers[3] == 'f' || cur_state->identifiers[3] == 'p')) ||
+						 cur_state->identifiers[1] == 's') &&
 						cur_state->paren_depth == 0)
 					{
 						if (pg_strcasecmp(yytext, "begin") == 0)
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 6ed85ef7446..a867786899b 100644
--- a/src/test/modules/test_ddl_deparse/expected/create_schema.out
+++ b/src/test/modules/test_ddl_deparse/expected/create_schema.out
@@ -13,10 +13,46 @@ CREATE SCHEMA IF NOT EXISTS baz;
 NOTICE:  schema "baz" already exists, skipping
 CREATE SCHEMA element_test
   CREATE TABLE foo (id int)
-  CREATE VIEW bar AS SELECT * FROM foo;
+  CREATE VIEW bar AS SELECT * FROM foo
+  CREATE COLLATION coll (LOCALE="C")
+  CREATE DOMAIN d1 AS INT
+  CREATE FUNCTION et_add(int4, int4) RETURNS int4 LANGUAGE sql
+    AS 'SELECT $1 + $2'
+  CREATE PROCEDURE et_proc(int4, int4)
+    BEGIN ATOMIC SELECT et_add($1,$2); END
+  CREATE TYPE floatrange AS RANGE (subtype = float8, subtype_diff = float8mi)
+  CREATE TYPE ss AS (a int)
+  CREATE TYPE sss
+  CREATE TYPE rainbow AS ENUM ('red', 'orange')
+  CREATE TEXT SEARCH PARSER et_ts_prs
+    (start = prsd_start, gettoken = prsd_nexttoken, end = prsd_end,
+     lextypes = prsd_lextype)
+;
 NOTICE:  DDL test: type simple, tag CREATE SCHEMA
 NOTICE:  DDL test: type simple, tag CREATE TABLE
 NOTICE:  DDL test: type simple, tag CREATE VIEW
+NOTICE:  DDL test: type simple, tag CREATE COLLATION
+NOTICE:  DDL test: type simple, tag CREATE DOMAIN
+NOTICE:  DDL test: type simple, tag CREATE FUNCTION
+NOTICE:  DDL test: type simple, tag CREATE PROCEDURE
+NOTICE:  DDL test: type simple, tag CREATE TYPE
+NOTICE:  DDL test: type simple, tag CREATE TYPE
+NOTICE:  DDL test: type simple, tag CREATE TYPE
+NOTICE:  DDL test: type simple, tag CREATE TYPE
+NOTICE:  DDL test: type simple, tag CREATE TEXT SEARCH PARSER
+DROP SCHEMA element_test CASCADE;
+NOTICE:  drop cascades to 11 other objects
+DETAIL:  drop cascades to table element_test.foo
+drop cascades to view element_test.bar
+drop cascades to collation element_test.coll
+drop cascades to type element_test.d1
+drop cascades to function element_test.et_add(integer,integer)
+drop cascades to function element_test.et_proc(integer,integer)
+drop cascades to type element_test.floatrange
+drop cascades to type element_test.ss
+drop cascades to type element_test.sss
+drop cascades to type element_test.rainbow
+drop cascades to text search parser element_test.et_ts_prs
 CREATE SCHEMA regress_schema_1
 CREATE TABLE t4(
     b INT,
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 145aef2a75a..7ba641d06d6 100644
--- a/src/test/modules/test_ddl_deparse/sql/create_schema.sql
+++ b/src/test/modules/test_ddl_deparse/sql/create_schema.sql
@@ -14,7 +14,23 @@ CREATE SCHEMA IF NOT EXISTS baz;
 
 CREATE SCHEMA element_test
   CREATE TABLE foo (id int)
-  CREATE VIEW bar AS SELECT * FROM foo;
+  CREATE VIEW bar AS SELECT * FROM foo
+  CREATE COLLATION coll (LOCALE="C")
+  CREATE DOMAIN d1 AS INT
+  CREATE FUNCTION et_add(int4, int4) RETURNS int4 LANGUAGE sql
+    AS 'SELECT $1 + $2'
+  CREATE PROCEDURE et_proc(int4, int4)
+    BEGIN ATOMIC SELECT et_add($1,$2); END
+  CREATE TYPE floatrange AS RANGE (subtype = float8, subtype_diff = float8mi)
+  CREATE TYPE ss AS (a int)
+  CREATE TYPE sss
+  CREATE TYPE rainbow AS ENUM ('red', 'orange')
+  CREATE TEXT SEARCH PARSER et_ts_prs
+    (start = prsd_start, gettoken = prsd_nexttoken, end = prsd_end,
+     lextypes = prsd_lextype)
+;
+
+DROP SCHEMA element_test CASCADE;
 
 CREATE SCHEMA regress_schema_1
 CREATE TABLE t4(
diff --git a/src/test/regress/expected/create_schema.out b/src/test/regress/expected/create_schema.out
index b34b9988962..4186a4ca379 100644
--- a/src/test/regress/expected/create_schema.out
+++ b/src/test/regress/expected/create_schema.out
@@ -177,5 +177,135 @@ 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
+-- Test miscellaneous object types within CREATE SCHEMA.
+CREATE SCHEMA regress_schema_misc
+  CREATE AGGREGATE cs_sum(int4)
+    (
+        SFUNC = int4_sum(int8, int4),
+        STYPE = int8,
+        INITCOND = '0'
+    )
+  CREATE COLLATION cs_builtin_c ( PROVIDER = builtin, LOCALE = "C" )
+  CREATE DOMAIN cs_positive AS integer CHECK (VALUE > 0)
+  CREATE FUNCTION cs_add(int4, int4) returns int4 language sql
+    as 'select $1 + $2'
+  CREATE OPERATOR + (function = cs_add, leftarg = int4, rightarg = int4)
+  CREATE PROCEDURE cs_proc(int4, int4)
+    BEGIN ATOMIC SELECT cs_add($1,$2); END
+  CREATE TEXT SEARCH CONFIGURATION cs_ts_conf (copy=english)
+  CREATE TEXT SEARCH DICTIONARY cs_ts_dict (template=simple)
+  CREATE TEXT SEARCH PARSER cs_ts_prs
+    (start = prsd_start, gettoken = prsd_nexttoken, end = prsd_end,
+     lextypes = prsd_lextype)
+  CREATE TEXT SEARCH TEMPLATE cs_ts_temp (lexize=dsimple_lexize)
+  CREATE TYPE regress_schema_misc.cs_enum AS ENUM ('red', 'orange')
+  CREATE TYPE cs_composite AS (a int, b float8)
+  CREATE TYPE cs_range AS RANGE (subtype = float8, subtype_diff = float8mi)
+  -- demonstrate creation of a base type with its I/O functions
+  CREATE TYPE cs_type
+  CREATE FUNCTION cs_type_in(cstring)
+    RETURNS cs_type LANGUAGE internal IMMUTABLE PARALLEL SAFE STRICT
+    AS 'int4in'
+  CREATE FUNCTION cs_type_out(cs_type)
+    RETURNS cstring LANGUAGE internal IMMUTABLE PARALLEL SAFE STRICT
+    AS 'int4out'
+  CREATE TYPE cs_type (
+    INPUT = cs_type_in,
+    OUTPUT = cs_type_out,
+    LIKE = int4
+  )
+;
+NOTICE:  return type cs_type is only a shell
+NOTICE:  argument type cs_type is only a shell
+LINE 29:   CREATE FUNCTION cs_type_out(cs_type)
+                                       ^
+\df regress_schema_misc.*
+                                                     List of functions
+       Schema        |     Name      |         Result data type          |           Argument data types            | Type 
+---------------------+---------------+-----------------------------------+------------------------------------------+------
+ regress_schema_misc | cs_add        | integer                           | integer, integer                         | func
+ regress_schema_misc | cs_multirange | regress_schema_misc.cs_multirange |                                          | func
+ regress_schema_misc | cs_multirange | regress_schema_misc.cs_multirange | VARIADIC regress_schema_misc.cs_range[]  | func
+ regress_schema_misc | cs_multirange | regress_schema_misc.cs_multirange | regress_schema_misc.cs_range             | func
+ regress_schema_misc | cs_proc       |                                   | IN integer, IN integer                   | proc
+ regress_schema_misc | cs_range      | regress_schema_misc.cs_range      | double precision, double precision       | func
+ regress_schema_misc | cs_range      | regress_schema_misc.cs_range      | double precision, double precision, text | func
+ regress_schema_misc | cs_sum        | bigint                            | integer                                  | agg
+ regress_schema_misc | cs_type_in    | regress_schema_misc.cs_type       | cstring                                  | func
+ regress_schema_misc | cs_type_out   | cstring                           | regress_schema_misc.cs_type              | func
+(10 rows)
+
+\do regress_schema_misc.+
+                                    List of operators
+       Schema        | Name | Left arg type | Right arg type | Result type | Description 
+---------------------+------+---------------+----------------+-------------+-------------
+ regress_schema_misc | +    | integer       | integer        | integer     | 
+(1 row)
+
+\dO regress_schema_misc.*
+                                          List of collations
+       Schema        |     Name     | Provider | Collate | Ctype | Locale | ICU Rules | Deterministic? 
+---------------------+--------------+----------+---------+-------+--------+-----------+----------------
+ regress_schema_misc | cs_builtin_c | builtin  |         |       | C      |           | yes
+(1 row)
+
+\dT regress_schema_misc.*
+                          List of data types
+       Schema        |               Name                | Description 
+---------------------+-----------------------------------+-------------
+ regress_schema_misc | regress_schema_misc.cs_composite  | 
+ regress_schema_misc | regress_schema_misc.cs_enum       | 
+ regress_schema_misc | regress_schema_misc.cs_multirange | 
+ regress_schema_misc | regress_schema_misc.cs_positive   | 
+ regress_schema_misc | regress_schema_misc.cs_range      | 
+ regress_schema_misc | regress_schema_misc.cs_type       | 
+(6 rows)
+
+\dF regress_schema_misc.*
+       List of text search configurations
+       Schema        |    Name    | Description 
+---------------------+------------+-------------
+ regress_schema_misc | cs_ts_conf | 
+(1 row)
+
+\dFd regress_schema_misc.*
+        List of text search dictionaries
+       Schema        |    Name    | Description 
+---------------------+------------+-------------
+ regress_schema_misc | cs_ts_dict | 
+(1 row)
+
+\dFp regress_schema_misc.*
+          List of text search parsers
+       Schema        |   Name    | Description 
+---------------------+-----------+-------------
+ regress_schema_misc | cs_ts_prs | 
+(1 row)
+
+\dFt regress_schema_misc.*
+         List of text search templates
+       Schema        |    Name    | Description 
+---------------------+------------+-------------
+ regress_schema_misc | cs_ts_temp | 
+(1 row)
+
+DROP SCHEMA regress_schema_misc CASCADE;
+NOTICE:  drop cascades to 16 other objects
+DETAIL:  drop cascades to function regress_schema_misc.cs_sum(integer)
+drop cascades to collation regress_schema_misc.cs_builtin_c
+drop cascades to type regress_schema_misc.cs_positive
+drop cascades to function regress_schema_misc.cs_add(integer,integer)
+drop cascades to operator regress_schema_misc.+(integer,integer)
+drop cascades to function regress_schema_misc.cs_proc(integer,integer)
+drop cascades to text search configuration regress_schema_misc.cs_ts_conf
+drop cascades to text search dictionary regress_schema_misc.cs_ts_dict
+drop cascades to text search parser regress_schema_misc.cs_ts_prs
+drop cascades to text search template regress_schema_misc.cs_ts_temp
+drop cascades to type regress_schema_misc.cs_enum
+drop cascades to type regress_schema_misc.cs_composite
+drop cascades to type regress_schema_misc.cs_range
+drop cascades to function regress_schema_misc.cs_type_out(regress_schema_misc.cs_type)
+drop cascades to type regress_schema_misc.cs_type
+drop cascades to function regress_schema_misc.cs_type_in(cstring)
 -- Clean up
 DROP ROLE regress_create_schema_role;
diff --git a/src/test/regress/sql/create_schema.sql b/src/test/regress/sql/create_schema.sql
index 0f2accc59ec..f580515dd0c 100644
--- a/src/test/regress/sql/create_schema.sql
+++ b/src/test/regress/sql/create_schema.sql
@@ -98,5 +98,72 @@ CREATE SCHEMA regress_schema_fk
 
 DROP SCHEMA regress_schema_fk CASCADE;
 
+-- Test miscellaneous object types within CREATE SCHEMA.
+CREATE SCHEMA regress_schema_misc
+  CREATE AGGREGATE cs_sum(int4)
+    (
+        SFUNC = int4_sum(int8, int4),
+        STYPE = int8,
+        INITCOND = '0'
+    )
+
+  CREATE COLLATION cs_builtin_c ( PROVIDER = builtin, LOCALE = "C" )
+
+  CREATE DOMAIN cs_positive AS integer CHECK (VALUE > 0)
+
+  CREATE FUNCTION cs_add(int4, int4) returns int4 language sql
+    as 'select $1 + $2'
+
+  CREATE OPERATOR + (function = cs_add, leftarg = int4, rightarg = int4)
+
+  CREATE PROCEDURE cs_proc(int4, int4)
+    BEGIN ATOMIC SELECT cs_add($1,$2); END
+
+  CREATE TEXT SEARCH CONFIGURATION cs_ts_conf (copy=english)
+
+  CREATE TEXT SEARCH DICTIONARY cs_ts_dict (template=simple)
+
+  CREATE TEXT SEARCH PARSER cs_ts_prs
+    (start = prsd_start, gettoken = prsd_nexttoken, end = prsd_end,
+     lextypes = prsd_lextype)
+
+  CREATE TEXT SEARCH TEMPLATE cs_ts_temp (lexize=dsimple_lexize)
+
+  CREATE TYPE regress_schema_misc.cs_enum AS ENUM ('red', 'orange')
+
+  CREATE TYPE cs_composite AS (a int, b float8)
+
+  CREATE TYPE cs_range AS RANGE (subtype = float8, subtype_diff = float8mi)
+
+  -- demonstrate creation of a base type with its I/O functions
+
+  CREATE TYPE cs_type
+
+  CREATE FUNCTION cs_type_in(cstring)
+    RETURNS cs_type LANGUAGE internal IMMUTABLE PARALLEL SAFE STRICT
+    AS 'int4in'
+
+  CREATE FUNCTION cs_type_out(cs_type)
+    RETURNS cstring LANGUAGE internal IMMUTABLE PARALLEL SAFE STRICT
+    AS 'int4out'
+
+  CREATE TYPE cs_type (
+    INPUT = cs_type_in,
+    OUTPUT = cs_type_out,
+    LIKE = int4
+  )
+;
+
+\df regress_schema_misc.*
+\do regress_schema_misc.+
+\dO regress_schema_misc.*
+\dT regress_schema_misc.*
+\dF regress_schema_misc.*
+\dFd regress_schema_misc.*
+\dFp regress_schema_misc.*
+\dFt regress_schema_misc.*
+
+DROP SCHEMA regress_schema_misc CASCADE;
+
 -- Clean up
 DROP ROLE regress_create_schema_role;
-- 
2.43.7



^ permalink  raw  reply  [nested|flat] 32+ messages in thread

* Re: CREATE SCHEMA ... CREATE DOMAIN support
  2024-11-30 19:08 Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  2024-12-03 19:35 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Peter Eisentraut <[email protected]>
  2024-12-03 20:07   ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  2024-12-11 06:22     ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <[email protected]>
  2024-12-11 17:08       ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  2025-08-19 03:36         ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <[email protected]>
  2025-08-22 08:59           ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <[email protected]>
  2025-09-02 21:24             ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  2025-12-17 13:29               ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <[email protected]>
  2026-04-03 18:35                 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  2026-04-05 18:42                   ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  2026-04-05 20:15                     ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
@ 2026-04-05 22:05                       ` Tom Lane <[email protected]>
  1 sibling, 0 replies; 32+ messages in thread

From: Tom Lane @ 2026-04-05 22:05 UTC (permalink / raw)
  To: jian he <[email protected]>; +Cc: Kirill Reshke <[email protected]>; Peter Eisentraut <[email protected]>; PostgreSQL Hackers <[email protected]>

I wrote:
> Here's a revised patchset that I think is pretty close to committable.

The cfbot reminded me that I'd failed to check whether the regression
test outputs are stable across different collations.  Grumble.

Fixed that, and rewrote the changes in create_schema.sgml as I'd been
speculating about earlier.

			regards, tom lane



Attachments:

  [text/x-diff] v13-0001-Don-t-try-to-re-order-the-subcommands-of-CREATE-.patch (26.9K, 2-v13-0001-Don-t-try-to-re-order-the-subcommands-of-CREATE-.patch)
  download | inline diff:
From 4f2ecd1698799bf145b4e1b67cf715e206128f0f Mon Sep 17 00:00:00 2001
From: Tom Lane <[email protected]>
Date: Sun, 5 Apr 2026 17:39:03 -0400
Subject: [PATCH v13 1/3] Don't try to re-order the subcommands of CREATE
 SCHEMA.

transformCreateSchemaStmtElements has always believed that it is
supposed to re-order the subcommands of CREATE SCHEMA into a safe
execution order.  However, it is nowhere near being capable of doing
that correctly.  Nor is there reason to think that it ever will be,
or that that is a well-defined requirement.  (The SQL standard does
say that it should be possible to do foreign-key forward references
within CREATE SCHEMA, but it's not clear that the text requires
anything more than that.)  Moreover, the problem will get worse as
we add more subcommand types.  Let's just drop the whole idea and
execute the commands in the order given, which seems like a much
less astonishment-prone definition anyway.  The foreign-key issue
will be handled in a follow-up patch.

Along the way, pass down a ParseState so that we can provide an
error cursor for "wrong schema name" and related errors, and fix
transformCreateSchemaStmtElements so that it doesn't scribble
on the parsetree passed to it.

Author: Tom Lane <[email protected]>
Reviewed-by: Kirill Reshke <[email protected]>
Reviewed-by: jian he <[email protected]>
Discussion: https://postgr.es/m/[email protected]
---
 doc/src/sgml/ref/create_schema.sgml         |  17 ++-
 src/backend/commands/extension.c            |   7 +-
 src/backend/commands/schemacmds.c           |  14 +--
 src/backend/parser/parse_utilcmd.c          | 131 ++++++++------------
 src/backend/tcop/utility.c                  |   4 +-
 src/include/commands/schemacmds.h           |   7 +-
 src/include/parser/parse_utilcmd.h          |   3 +-
 src/test/regress/expected/create_schema.out |  37 ++++++
 src/test/regress/expected/create_view.out   |   2 +
 src/test/regress/expected/event_trigger.out |   2 +-
 src/test/regress/expected/namespace.out     |   9 +-
 src/test/regress/sql/create_schema.sql      |   5 +
 src/test/regress/sql/namespace.sql          |  11 +-
 src/tools/pgindent/typedefs.list            |   1 -
 14 files changed, 138 insertions(+), 112 deletions(-)

diff --git a/doc/src/sgml/ref/create_schema.sgml b/doc/src/sgml/ref/create_schema.sgml
index ed69298ccc6..9e6f0e24339 100644
--- a/doc/src/sgml/ref/create_schema.sgml
+++ b/doc/src/sgml/ref/create_schema.sgml
@@ -131,6 +131,11 @@ CREATE SCHEMA IF NOT EXISTS AUTHORIZATION <replaceable class="parameter">role_sp
    <literal>CREATE</literal> privilege for the current database.
    (Of course, superusers bypass this check.)
   </para>
+
+  <para>
+   The <replaceable class="parameter">schema_element</replaceable>
+   subcommands, if any, are executed in the order they are written.
+  </para>
  </refsect1>
 
  <refsect1>
@@ -193,12 +198,12 @@ CREATE VIEW hollywood.winners AS
   </para>
 
   <para>
-   The SQL standard specifies that the subcommands in <command>CREATE
-   SCHEMA</command> can appear in any order.  The present
-   <productname>PostgreSQL</productname> implementation does not
-   handle all cases of forward references in subcommands; it might
-   sometimes be necessary to reorder the subcommands in order to avoid
-   forward references.
+   Some other SQL implementations attempt to allow more kinds of forward
+   references to objects defined in
+   later <replaceable class="parameter">schema_element</replaceable>
+   subcommands than just foreign key constraints.  This is difficult or
+   impossible to do correctly in general, and it is not clear that the SQL
+   standard requires any such behavior except for foreign keys.
   </para>
 
   <para>
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index b98801d08f2..a330b5fd6ce 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1952,14 +1952,17 @@ CreateExtensionInternal(char *extensionName,
 
 		if (!OidIsValid(schemaOid))
 		{
+			ParseState *pstate = make_parsestate(NULL);
 			CreateSchemaStmt *csstmt = makeNode(CreateSchemaStmt);
 
+			pstate->p_sourcetext = "(generated CREATE SCHEMA command)";
+
 			csstmt->schemaname = schemaName;
 			csstmt->authrole = NULL;	/* will be created by current user */
 			csstmt->schemaElts = NIL;
 			csstmt->if_not_exists = false;
-			CreateSchemaCommand(csstmt, "(generated CREATE SCHEMA command)",
-								-1, -1);
+
+			CreateSchemaCommand(pstate, csstmt, -1, -1);
 
 			/*
 			 * CreateSchemaCommand includes CommandCounterIncrement, so new
diff --git a/src/backend/commands/schemacmds.c b/src/backend/commands/schemacmds.c
index 4c51e920626..bfaa4743cd8 100644
--- a/src/backend/commands/schemacmds.c
+++ b/src/backend/commands/schemacmds.c
@@ -49,7 +49,7 @@ static void AlterSchemaOwner_internal(HeapTuple tup, Relation rel, Oid newOwnerI
  * a subquery.
  */
 Oid
-CreateSchemaCommand(CreateSchemaStmt *stmt, const char *queryString,
+CreateSchemaCommand(ParseState *pstate, CreateSchemaStmt *stmt,
 					int stmt_location, int stmt_len)
 {
 	const char *schemaName = stmt->schemaname;
@@ -189,12 +189,12 @@ CreateSchemaCommand(CreateSchemaStmt *stmt, const char *queryString,
 
 	/*
 	 * Examine the list of commands embedded in the CREATE SCHEMA command, and
-	 * reorganize them into a sequentially executable order with no forward
-	 * references.  Note that the result is still a list of raw parsetrees ---
-	 * we cannot, in general, run parse analysis on one statement until we
-	 * have actually executed the prior ones.
+	 * do preliminary transformations.  Note that the result is still a list
+	 * of raw parsetrees --- we cannot, in general, run parse analysis on one
+	 * statement until we have actually executed the prior ones.
 	 */
-	parsetree_list = transformCreateSchemaStmtElements(stmt->schemaElts,
+	parsetree_list = transformCreateSchemaStmtElements(pstate,
+													   stmt->schemaElts,
 													   schemaName);
 
 	/*
@@ -219,7 +219,7 @@ CreateSchemaCommand(CreateSchemaStmt *stmt, const char *queryString,
 
 		/* do this step */
 		ProcessUtility(wrapper,
-					   queryString,
+					   pstate->p_sourcetext,
 					   false,
 					   PROCESS_UTILITY_SUBCOMMAND,
 					   NULL,
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 9a918e14aa7..cb2e57610b2 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -99,18 +99,6 @@ typedef struct
 	bool		ofType;			/* true if statement contains OF typename */
 } CreateStmtContext;
 
-/* State shared by transformCreateSchemaStmtElements and its subroutines */
-typedef struct
-{
-	const char *schemaname;		/* name of schema */
-	List	   *sequences;		/* CREATE SEQUENCE items */
-	List	   *tables;			/* CREATE TABLE items */
-	List	   *views;			/* CREATE VIEW items */
-	List	   *indexes;		/* CREATE INDEX items */
-	List	   *triggers;		/* CREATE TRIGGER items */
-	List	   *grants;			/* GRANT items */
-} CreateSchemaStmtContext;
-
 
 static void transformColumnDefinition(CreateStmtContext *cxt,
 									  ColumnDef *column);
@@ -137,7 +125,8 @@ static void transformCheckConstraints(CreateStmtContext *cxt,
 static void transformConstraintAttrs(CreateStmtContext *cxt,
 									 List *constraintList);
 static void transformColumnType(CreateStmtContext *cxt, ColumnDef *column);
-static void setSchemaName(const char *context_schema, char **stmt_schema_name);
+static void checkSchemaName(ParseState *pstate, const char *context_schema,
+							RangeVar *relation);
 static void transformPartitionCmd(CreateStmtContext *cxt, PartitionBoundSpec *bound);
 static List *transformPartitionRangeBounds(ParseState *pstate, List *blist,
 										   Relation parent);
@@ -4395,51 +4384,35 @@ transformColumnType(CreateStmtContext *cxt, ColumnDef *column)
  * transformCreateSchemaStmtElements -
  *	  analyzes the elements of a CREATE SCHEMA statement
  *
- * Split the schema element list from a CREATE SCHEMA statement into
- * individual commands and place them in the result list in an order
- * such that there are no forward references (e.g. GRANT to a table
- * created later in the list). Note that the logic we use for determining
- * forward references is presently quite incomplete.
+ * 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.
+ * 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.
  *
  * "schemaName" is the name of the schema that will be used for the creation
- * of the objects listed, that may be compiled from the schema name defined
+ * of the objects listed.  It may be obtained from the schema name defined
  * in the statement or a role specification.
  *
- * SQL also allows constraints to make forward references, so thumb through
- * the table columns and move forward references to a posterior alter-table
- * command.
- *
  * 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: this breaks the rules a little bit by modifying schema-name fields
- * within passed-in structs.  However, the transformation would be the same
- * if done over, so it should be all right to scribble on the input to this
- * extent.
  */
 List *
-transformCreateSchemaStmtElements(List *schemaElts, const char *schemaName)
+transformCreateSchemaStmtElements(ParseState *pstate, List *schemaElts,
+								  const char *schemaName)
 {
-	CreateSchemaStmtContext cxt;
-	List	   *result;
-	ListCell   *elements;
-
-	cxt.schemaname = schemaName;
-	cxt.sequences = NIL;
-	cxt.tables = NIL;
-	cxt.views = NIL;
-	cxt.indexes = NIL;
-	cxt.triggers = NIL;
-	cxt.grants = NIL;
+	List	   *elements = NIL;
+	ListCell   *lc;
 
 	/*
-	 * Run through each schema element in the schema element list. Separate
-	 * statements by type, and do preliminary analysis.
+	 * Run through each schema element in the schema element list.  Check
+	 * target schema names, and collect the list of actions to be done.
 	 */
-	foreach(elements, schemaElts)
+	foreach(lc, schemaElts)
 	{
-		Node	   *element = lfirst(elements);
+		Node	   *element = lfirst(lc);
 
 		switch (nodeTag(element))
 		{
@@ -4447,8 +4420,8 @@ transformCreateSchemaStmtElements(List *schemaElts, const char *schemaName)
 				{
 					CreateSeqStmt *elp = (CreateSeqStmt *) element;
 
-					setSchemaName(cxt.schemaname, &elp->sequence->schemaname);
-					cxt.sequences = lappend(cxt.sequences, element);
+					checkSchemaName(pstate, schemaName, elp->sequence);
+					elements = lappend(elements, element);
 				}
 				break;
 
@@ -4456,12 +4429,8 @@ transformCreateSchemaStmtElements(List *schemaElts, const char *schemaName)
 				{
 					CreateStmt *elp = (CreateStmt *) element;
 
-					setSchemaName(cxt.schemaname, &elp->relation->schemaname);
-
-					/*
-					 * XXX todo: deal with constraints
-					 */
-					cxt.tables = lappend(cxt.tables, element);
+					checkSchemaName(pstate, schemaName, elp->relation);
+					elements = lappend(elements, element);
 				}
 				break;
 
@@ -4469,12 +4438,8 @@ transformCreateSchemaStmtElements(List *schemaElts, const char *schemaName)
 				{
 					ViewStmt   *elp = (ViewStmt *) element;
 
-					setSchemaName(cxt.schemaname, &elp->view->schemaname);
-
-					/*
-					 * XXX todo: deal with references between views
-					 */
-					cxt.views = lappend(cxt.views, element);
+					checkSchemaName(pstate, schemaName, elp->view);
+					elements = lappend(elements, element);
 				}
 				break;
 
@@ -4482,8 +4447,8 @@ transformCreateSchemaStmtElements(List *schemaElts, const char *schemaName)
 				{
 					IndexStmt  *elp = (IndexStmt *) element;
 
-					setSchemaName(cxt.schemaname, &elp->relation->schemaname);
-					cxt.indexes = lappend(cxt.indexes, element);
+					checkSchemaName(pstate, schemaName, elp->relation);
+					elements = lappend(elements, element);
 				}
 				break;
 
@@ -4491,13 +4456,13 @@ transformCreateSchemaStmtElements(List *schemaElts, const char *schemaName)
 				{
 					CreateTrigStmt *elp = (CreateTrigStmt *) element;
 
-					setSchemaName(cxt.schemaname, &elp->relation->schemaname);
-					cxt.triggers = lappend(cxt.triggers, element);
+					checkSchemaName(pstate, schemaName, elp->relation);
+					elements = lappend(elements, element);
 				}
 				break;
 
 			case T_GrantStmt:
-				cxt.grants = lappend(cxt.grants, element);
+				elements = lappend(elements, element);
 				break;
 
 			default:
@@ -4506,32 +4471,40 @@ transformCreateSchemaStmtElements(List *schemaElts, const char *schemaName)
 		}
 	}
 
-	result = NIL;
-	result = list_concat(result, cxt.sequences);
-	result = list_concat(result, cxt.tables);
-	result = list_concat(result, cxt.views);
-	result = list_concat(result, cxt.indexes);
-	result = list_concat(result, cxt.triggers);
-	result = list_concat(result, cxt.grants);
-
-	return result;
+	return elements;
 }
 
 /*
- * setSchemaName
- *		Set or check schema name in an element of a CREATE SCHEMA command
+ * checkSchemaName
+ *		Check schema name in an element of a CREATE SCHEMA command
+ *
+ * It's okay if the command doesn't specify a target schema name, because
+ * CreateSchemaCommand will set up the default creation schema to be the
+ * new schema.  But if a target schema name is given, it had better match.
+ * We also have to check that the command doesn't say CREATE TEMP, since
+ * that would likewise put the object into the wrong schema.
  */
 static void
-setSchemaName(const char *context_schema, char **stmt_schema_name)
+checkSchemaName(ParseState *pstate, const char *context_schema,
+				RangeVar *relation)
 {
-	if (*stmt_schema_name == NULL)
-		*stmt_schema_name = unconstify(char *, context_schema);
-	else if (strcmp(context_schema, *stmt_schema_name) != 0)
+	if (relation->schemaname != NULL &&
+		strcmp(context_schema, relation->schemaname) != 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_SCHEMA_DEFINITION),
 				 errmsg("CREATE specifies a schema (%s) "
 						"different from the one being created (%s)",
-						*stmt_schema_name, context_schema)));
+						relation->schemaname, context_schema),
+				 parser_errposition(pstate, relation->location)));
+
+	if (relation->relpersistence == RELPERSISTENCE_TEMP)
+	{
+		/* spell this error the same as in RangeVarAdjustRelationPersistence */
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+				 errmsg("cannot create temporary relation in non-temporary schema"),
+				 parser_errposition(pstate, relation->location)));
+	}
 }
 
 /*
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 2b609bfc824..832d7647904 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1122,8 +1122,8 @@ ProcessUtilitySlow(ParseState *pstate,
 				 * relation and attribute manipulation
 				 */
 			case T_CreateSchemaStmt:
-				CreateSchemaCommand((CreateSchemaStmt *) parsetree,
-									queryString,
+				CreateSchemaCommand(pstate,
+									(CreateSchemaStmt *) parsetree,
 									pstmt->stmt_location,
 									pstmt->stmt_len);
 
diff --git a/src/include/commands/schemacmds.h b/src/include/commands/schemacmds.h
index d100e4691b3..b4348436dd3 100644
--- a/src/include/commands/schemacmds.h
+++ b/src/include/commands/schemacmds.h
@@ -16,12 +16,11 @@
 #define SCHEMACMDS_H
 
 #include "catalog/objectaddress.h"
-#include "nodes/parsenodes.h"
+#include "parser/parse_node.h"
 
-extern Oid	CreateSchemaCommand(CreateSchemaStmt *stmt,
-								const char *queryString,
+extern Oid	CreateSchemaCommand(ParseState *pstate,
+								CreateSchemaStmt *stmt,
 								int stmt_location, int stmt_len);
-
 extern ObjectAddress RenameSchema(const char *oldname, const char *newname);
 extern ObjectAddress AlterSchemaOwner(const char *name, Oid newOwnerId);
 extern void AlterSchemaOwner_oid(Oid schemaoid, Oid newOwnerId);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 53f282f77ec..34c98e5122f 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -30,7 +30,8 @@ extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-extern List *transformCreateSchemaStmtElements(List *schemaElts,
+extern List *transformCreateSchemaStmtElements(ParseState *pstate,
+											   List *schemaElts,
 											   const char *schemaName);
 extern PartitionBoundSpec *transformPartitionBound(ParseState *pstate, Relation parent,
 												   PartitionBoundSpec *spec);
diff --git a/src/test/regress/expected/create_schema.out b/src/test/regress/expected/create_schema.out
index 93302a07efc..4ab947a60a8 100644
--- a/src/test/regress/expected/create_schema.out
+++ b/src/test/regress/expected/create_schema.out
@@ -9,55 +9,92 @@ CREATE ROLE regress_create_schema_role SUPERUSER;
 CREATE SCHEMA AUTHORIZATION regress_create_schema_role
   CREATE SEQUENCE schema_not_existing.seq;
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_create_schema_role)
+LINE 2:   CREATE SEQUENCE schema_not_existing.seq;
+                          ^
 CREATE SCHEMA AUTHORIZATION regress_create_schema_role
   CREATE TABLE schema_not_existing.tab (id int);
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_create_schema_role)
+LINE 2:   CREATE TABLE schema_not_existing.tab (id int);
+                       ^
 CREATE SCHEMA AUTHORIZATION regress_create_schema_role
   CREATE VIEW schema_not_existing.view AS SELECT 1;
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_create_schema_role)
+LINE 2:   CREATE VIEW schema_not_existing.view AS SELECT 1;
+                      ^
 CREATE SCHEMA AUTHORIZATION regress_create_schema_role
   CREATE INDEX ON schema_not_existing.tab (id);
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_create_schema_role)
+LINE 2:   CREATE INDEX ON schema_not_existing.tab (id);
+                          ^
 CREATE SCHEMA AUTHORIZATION regress_create_schema_role
   CREATE TRIGGER schema_trig BEFORE INSERT ON schema_not_existing.tab
   EXECUTE FUNCTION schema_trig.no_func();
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_create_schema_role)
+LINE 2:   CREATE TRIGGER schema_trig BEFORE INSERT ON schema_not_exi...
+                                                      ^
 -- Again, with a role specification and no schema names.
 SET ROLE regress_create_schema_role;
 CREATE SCHEMA AUTHORIZATION CURRENT_ROLE
   CREATE SEQUENCE schema_not_existing.seq;
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_create_schema_role)
+LINE 2:   CREATE SEQUENCE schema_not_existing.seq;
+                          ^
 CREATE SCHEMA AUTHORIZATION CURRENT_ROLE
   CREATE TABLE schema_not_existing.tab (id int);
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_create_schema_role)
+LINE 2:   CREATE TABLE schema_not_existing.tab (id int);
+                       ^
 CREATE SCHEMA AUTHORIZATION CURRENT_ROLE
   CREATE VIEW schema_not_existing.view AS SELECT 1;
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_create_schema_role)
+LINE 2:   CREATE VIEW schema_not_existing.view AS SELECT 1;
+                      ^
 CREATE SCHEMA AUTHORIZATION CURRENT_ROLE
   CREATE INDEX ON schema_not_existing.tab (id);
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_create_schema_role)
+LINE 2:   CREATE INDEX ON schema_not_existing.tab (id);
+                          ^
 CREATE SCHEMA AUTHORIZATION CURRENT_ROLE
   CREATE TRIGGER schema_trig BEFORE INSERT ON schema_not_existing.tab
   EXECUTE FUNCTION schema_trig.no_func();
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_create_schema_role)
+LINE 2:   CREATE TRIGGER schema_trig BEFORE INSERT ON schema_not_exi...
+                                                      ^
 -- Again, with a schema name and a role specification.
 CREATE SCHEMA regress_schema_1 AUTHORIZATION CURRENT_ROLE
   CREATE SEQUENCE schema_not_existing.seq;
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_schema_1)
+LINE 2:   CREATE SEQUENCE schema_not_existing.seq;
+                          ^
 CREATE SCHEMA regress_schema_1 AUTHORIZATION CURRENT_ROLE
   CREATE TABLE schema_not_existing.tab (id int);
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_schema_1)
+LINE 2:   CREATE TABLE schema_not_existing.tab (id int);
+                       ^
 CREATE SCHEMA regress_schema_1 AUTHORIZATION CURRENT_ROLE
   CREATE VIEW schema_not_existing.view AS SELECT 1;
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_schema_1)
+LINE 2:   CREATE VIEW schema_not_existing.view AS SELECT 1;
+                      ^
 CREATE SCHEMA regress_schema_1 AUTHORIZATION CURRENT_ROLE
   CREATE INDEX ON schema_not_existing.tab (id);
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_schema_1)
+LINE 2:   CREATE INDEX ON schema_not_existing.tab (id);
+                          ^
 CREATE SCHEMA regress_schema_1 AUTHORIZATION CURRENT_ROLE
   CREATE TRIGGER schema_trig BEFORE INSERT ON schema_not_existing.tab
   EXECUTE FUNCTION schema_trig.no_func();
 ERROR:  CREATE specifies a schema (schema_not_existing) different from the one being created (regress_schema_1)
+LINE 2:   CREATE TRIGGER schema_trig BEFORE INSERT ON schema_not_exi...
+                                                      ^
 RESET ROLE;
+-- Forward references no longer work in general.
+CREATE SCHEMA regress_schema_1 AUTHORIZATION CURRENT_ROLE
+  CREATE VIEW abcd_view AS SELECT a FROM abcd
+  CREATE TABLE abcd (a int);
+ERROR:  relation "abcd" does not exist
+LINE 2:   CREATE VIEW abcd_view AS SELECT a FROM abcd
+                                                 ^
 -- Cases where the schema creation succeeds.
 -- The schema created matches the role name.
 CREATE SCHEMA AUTHORIZATION regress_create_schema_role
diff --git a/src/test/regress/expected/create_view.out b/src/test/regress/expected/create_view.out
index bc4f79938b3..63cf4b4371d 100644
--- a/src/test/regress/expected/create_view.out
+++ b/src/test/regress/expected/create_view.out
@@ -128,6 +128,8 @@ ERROR:  cannot create temporary relation in non-temporary schema
 CREATE SCHEMA test_view_schema
     CREATE TEMP VIEW testview AS SELECT 1;
 ERROR:  cannot create temporary relation in non-temporary schema
+LINE 2:     CREATE TEMP VIEW testview AS SELECT 1;
+                             ^
 -- joins: if any of the join relations are temporary, the view
 -- should also be temporary
 -- should be non-temp
diff --git a/src/test/regress/expected/event_trigger.out b/src/test/regress/expected/event_trigger.out
index f897b079e67..4c32e1dcaf5 100644
--- a/src/test/regress/expected/event_trigger.out
+++ b/src/test/regress/expected/event_trigger.out
@@ -425,13 +425,13 @@ NOTICE:  END: command_tag=CREATE TABLE type=table identity=evttrig.one
 NOTICE:  END: command_tag=CREATE INDEX type=index identity=evttrig.one_pkey
 NOTICE:  END: command_tag=ALTER SEQUENCE type=sequence identity=evttrig.one_col_a_seq
 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=CREATE INDEX type=index identity=evttrig.one_idx
 -- 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/expected/namespace.out b/src/test/regress/expected/namespace.out
index dbbda72d395..2e582e783c2 100644
--- a/src/test/regress/expected/namespace.out
+++ b/src/test/regress/expected/namespace.out
@@ -10,13 +10,14 @@ SELECT pg_catalog.set_config('search_path', ' ', false);
 (1 row)
 
 CREATE SCHEMA test_ns_schema_1
-       CREATE UNIQUE INDEX abc_a_idx ON abc (a)
-       CREATE VIEW abc_view AS
-              SELECT a+1 AS a, b+1 AS b FROM abc
        CREATE TABLE abc (
               a serial,
               b int UNIQUE
-       );
+       )
+       CREATE UNIQUE INDEX abc_a_idx ON abc (a)
+       CREATE VIEW abc_view AS
+              SELECT a+1 AS a, b+1 AS b FROM abc
+;
 -- verify that the correct search_path restored on abort
 SET search_path to public;
 BEGIN;
diff --git a/src/test/regress/sql/create_schema.sql b/src/test/regress/sql/create_schema.sql
index 1b7064247a1..62651342114 100644
--- a/src/test/regress/sql/create_schema.sql
+++ b/src/test/regress/sql/create_schema.sql
@@ -47,6 +47,11 @@ CREATE SCHEMA regress_schema_1 AUTHORIZATION CURRENT_ROLE
   EXECUTE FUNCTION schema_trig.no_func();
 RESET ROLE;
 
+-- Forward references no longer work in general.
+CREATE SCHEMA regress_schema_1 AUTHORIZATION CURRENT_ROLE
+  CREATE VIEW abcd_view AS SELECT a FROM abcd
+  CREATE TABLE abcd (a int);
+
 -- Cases where the schema creation succeeds.
 -- The schema created matches the role name.
 CREATE SCHEMA AUTHORIZATION regress_create_schema_role
diff --git a/src/test/regress/sql/namespace.sql b/src/test/regress/sql/namespace.sql
index 306cdc2d8c6..a75d4f580d3 100644
--- a/src/test/regress/sql/namespace.sql
+++ b/src/test/regress/sql/namespace.sql
@@ -7,15 +7,16 @@
 SELECT pg_catalog.set_config('search_path', ' ', false);
 
 CREATE SCHEMA test_ns_schema_1
+       CREATE TABLE abc (
+              a serial,
+              b int UNIQUE
+       )
+
        CREATE UNIQUE INDEX abc_a_idx ON abc (a)
 
        CREATE VIEW abc_view AS
               SELECT a+1 AS a, b+1 AS b FROM abc
-
-       CREATE TABLE abc (
-              a serial,
-              b int UNIQUE
-       );
+;
 
 -- verify that the correct search_path restored on abort
 SET search_path to public;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 6a328fceaee..2e99972e0ad 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -585,7 +585,6 @@ CreateRangeStmt
 CreateReplicationSlotCmd
 CreateRoleStmt
 CreateSchemaStmt
-CreateSchemaStmtContext
 CreateSeqStmt
 CreateStatsStmt
 CreateStmt
-- 
2.43.7



  [text/x-diff] v13-0002-Execute-foreign-key-constraints-in-CREATE-SCHEMA.patch (24.3K, 3-v13-0002-Execute-foreign-key-constraints-in-CREATE-SCHEMA.patch)
  download | inline diff:
From af2bfc3990df675b20a8ea90196ab5b2e3093950 Mon Sep 17 00:00:00 2001
From: Tom Lane <[email protected]>
Date: Sun, 5 Apr 2026 17:45:07 -0400
Subject: [PATCH v13 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 <[email protected]>
Reviewed-by: Tom Lane <[email protected]>
Discussion: https://postgr.es/m/[email protected]
---
 doc/src/sgml/ref/create_schema.sgml           |   3 +
 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, 310 insertions(+), 23 deletions(-)

diff --git a/doc/src/sgml/ref/create_schema.sgml b/doc/src/sgml/ref/create_schema.sgml
index 9e6f0e24339..96bc496e777 100644
--- a/doc/src/sgml/ref/create_schema.sgml
+++ b/doc/src/sgml/ref/create_schema.sgml
@@ -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>
 
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



  [text/x-diff] v13-0003-Support-more-object-types-within-CREATE-SCHEMA.patch (25.2K, 4-v13-0003-Support-more-object-types-within-CREATE-SCHEMA.patch)
  download | inline diff:
From 6e477e230c468f4f959388a3a7059ef9a79cce08 Mon Sep 17 00:00:00 2001
From: Tom Lane <[email protected]>
Date: Sun, 5 Apr 2026 17:57:10 -0400
Subject: [PATCH v13 3/3] Support more object types within CREATE SCHEMA.

Having rejected the principle that we should know how to re-order
the sub-commands of CREATE SCHEMA, there is not really anything
except a little coding to stop us from supporting more object types.
This patch adds support for creating functions (including procedures
and aggregates), operators, types (including domains), collations,
and text search objects.

SQL:2021 specifies that we should allow functions, procedures,
types, domains, and collations, so this moves us a great deal
closer to full SQL compatibility of CREATE SCHEMA.  What remains
missing from their list are object types we don't have at all
(e.g. CREATE CHARACTER SET), casts, transforms, and roles.
Supporting casts or transforms would be problematic because
they don't have names at all, let alone schema-qualified names,
so it'd be quite a stretch to say that they belong to a schema.
Roles likewise are not schema-qualified, plus they are global
to a cluster, making it even less reasonable to consider them
as belonging to a schema.  So I don't see us trying to complete
the list.

User-defined aggregates and operators are outside the spec's ken,
as are text search objects, so adding them does not do anything for
spec compatibility.  But they go along with these other object types,
plus it takes no additional code to support them since they are
represented as DefineStmts like some variants of CREATE TYPE.
It would indeed take some effort to reject them.

Author: Kirill Reshke <[email protected]>
Author: Jian He <[email protected]>
Reviewed-by: Tom Lane <[email protected]>
Discussion: https://postgr.es/m/CALdSSPh4jUSDsWu3K58hjO60wnTRR0DuO4CKRcwa8EVuOSfXxg@mail.gmail.com
---
 doc/src/sgml/ref/create_schema.sgml           |  25 +++-
 src/backend/parser/gram.y                     |   3 +
 src/backend/parser/parse_utilcmd.c            |  91 +++++++++++-
 src/bin/psql/tab-complete.in.c                |  22 +--
 src/fe_utils/psqlscan.l                       |  22 +--
 .../expected/create_schema.out                |  38 ++++-
 .../test_ddl_deparse/sql/create_schema.sql    |  18 ++-
 src/test/regress/expected/create_schema.out   | 135 ++++++++++++++++++
 src/test/regress/sql/create_schema.sql        |  69 +++++++++
 9 files changed, 398 insertions(+), 25 deletions(-)

diff --git a/doc/src/sgml/ref/create_schema.sgml b/doc/src/sgml/ref/create_schema.sgml
index 96bc496e777..4ecf82d6bcb 100644
--- a/doc/src/sgml/ref/create_schema.sgml
+++ b/doc/src/sgml/ref/create_schema.sgml
@@ -100,12 +100,27 @@ CREATE SCHEMA IF NOT EXISTS AUTHORIZATION <replaceable class="parameter">role_sp
       <listitem>
        <para>
         An SQL statement defining an object to be created within the
-        schema. Currently, only <command>CREATE
-        TABLE</command>, <command>CREATE VIEW</command>, <command>CREATE
-        INDEX</command>, <command>CREATE SEQUENCE</command>, <command>CREATE
-        TRIGGER</command> and <command>GRANT</command> are accepted as clauses
+        schema. Currently, only
+        <command>CREATE AGGREGATE</command>,
+        <command>CREATE COLLATION</command>,
+        <command>CREATE DOMAIN</command>,
+        <command>CREATE FUNCTION</command>,
+        <command>CREATE INDEX</command>,
+        <command>CREATE OPERATOR</command>,
+        <command>CREATE PROCEDURE</command>,
+        <command>CREATE SEQUENCE</command>,
+        <command>CREATE TABLE</command>,
+        <command>CREATE TEXT SEARCH CONFIGURATION</command>,
+        <command>CREATE TEXT SEARCH DICTIONARY</command>,
+        <command>CREATE TEXT SEARCH PARSER</command>,
+        <command>CREATE TEXT SEARCH TEMPLATE</command>,
+        <command>CREATE TRIGGER</command>,
+        <command>CREATE TYPE</command>,
+        <command>CREATE VIEW</command>,
+        and <command>GRANT</command> are accepted as clauses
         within <command>CREATE SCHEMA</command>. Other kinds of objects may
-        be created in separate commands after the schema is created.
+        be created within the schema in separate commands after the schema
+        is created.
        </para>
       </listitem>
      </varlistentry>
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index f4a08baa95a..2d1b19d1f53 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -1679,8 +1679,11 @@ OptSchemaEltList:
 schema_stmt:
 			CreateStmt
 			| IndexStmt
+			| CreateDomainStmt
+			| CreateFunctionStmt
 			| CreateSeqStmt
 			| CreateTrigStmt
+			| DefineStmt
 			| GrantStmt
 			| ViewStmt
 		;
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 1bc5df7e0ef..9348e594dab 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -127,6 +127,8 @@ static void transformConstraintAttrs(ParseState *pstate,
 static void transformColumnType(CreateStmtContext *cxt, ColumnDef *column);
 static void checkSchemaName(ParseState *pstate, const char *context_schema,
 							RangeVar *relation);
+static void checkSchemaNameList(const char *context_schema,
+								List *qualified_name);
 static CreateStmt *transformCreateSchemaCreateTable(ParseState *pstate,
 													CreateStmt *stmt,
 													List **fk_elements);
@@ -4481,6 +4483,68 @@ transformCreateSchemaStmtElements(ParseState *pstate, List *schemaElts,
 				}
 				break;
 
+			case T_CreateDomainStmt:
+				{
+					CreateDomainStmt *elp = (CreateDomainStmt *) element;
+
+					checkSchemaNameList(schemaName, elp->domainname);
+					elements = lappend(elements, element);
+				}
+				break;
+
+			case T_CreateFunctionStmt:
+				{
+					CreateFunctionStmt *elp = (CreateFunctionStmt *) element;
+
+					checkSchemaNameList(schemaName, elp->funcname);
+					elements = lappend(elements, element);
+				}
+				break;
+
+				/*
+				 * CREATE TYPE can produce a DefineStmt, but also
+				 * CreateEnumStmt, CreateRangeStmt, and CompositeTypeStmt.
+				 * Allowing DefineStmt also provides support for several other
+				 * commands: currently, CREATE AGGREGATE, CREATE COLLATION,
+				 * CREATE OPERATOR, and text search objects.
+				 */
+
+			case T_DefineStmt:
+				{
+					DefineStmt *elp = (DefineStmt *) element;
+
+					checkSchemaNameList(schemaName, elp->defnames);
+					elements = lappend(elements, element);
+				}
+				break;
+
+			case T_CreateEnumStmt:
+				{
+					CreateEnumStmt *elp = (CreateEnumStmt *) element;
+
+					checkSchemaNameList(schemaName, elp->typeName);
+					elements = lappend(elements, element);
+				}
+				break;
+
+			case T_CreateRangeStmt:
+				{
+					CreateRangeStmt *elp = (CreateRangeStmt *) element;
+
+					checkSchemaNameList(schemaName, elp->typeName);
+					elements = lappend(elements, element);
+				}
+				break;
+
+			case T_CompositeTypeStmt:
+				{
+					CompositeTypeStmt *elp = (CompositeTypeStmt *) element;
+
+					checkSchemaName(pstate, schemaName, elp->typevar);
+					elements = lappend(elements, element);
+				}
+				break;
+
 			case T_GrantStmt:
 				elements = lappend(elements, element);
 				break;
@@ -4496,7 +4560,8 @@ transformCreateSchemaStmtElements(ParseState *pstate, List *schemaElts,
 
 /*
  * checkSchemaName
- *		Check schema name in an element of a CREATE SCHEMA command
+ *		Check schema name in an element of a CREATE SCHEMA command,
+ *		where the element's name is given by a RangeVar
  *
  * It's okay if the command doesn't specify a target schema name, because
  * CreateSchemaCommand will set up the default creation schema to be the
@@ -4527,6 +4592,30 @@ checkSchemaName(ParseState *pstate, const char *context_schema,
 	}
 }
 
+/*
+ * checkSchemaNameList
+ *		Check schema name in an element of a CREATE SCHEMA command,
+ *		where the element's name is given by a List
+ *
+ * Much as above, but we don't have to worry about TEMP.
+ * Sadly, this also means we don't have a parse location to report.
+ */
+static void
+checkSchemaNameList(const char *context_schema, List *qualified_name)
+{
+	char	   *obj_schema;
+	char	   *obj_name;
+
+	DeconstructQualifiedName(qualified_name, &obj_schema, &obj_name);
+	if (obj_schema != NULL &&
+		strcmp(context_schema, obj_schema) != 0)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_SCHEMA_DEFINITION),
+				 errmsg("CREATE specifies a schema (%s) "
+						"different from the one being created (%s)",
+						obj_schema, context_schema)));
+}
+
 /*
  * transformCreateSchemaCreateTable
  *		Process one CreateStmt for transformCreateSchemaStmtElements.
diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c
index 53bf1e21721..76d7edd4619 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -2210,7 +2210,11 @@ match_previous_words(int pattern_id,
 	{
 		/* only some object types can be created as part of CREATE SCHEMA */
 		if (HeadMatches("CREATE", "SCHEMA"))
-			COMPLETE_WITH("TABLE", "VIEW", "INDEX", "SEQUENCE", "TRIGGER",
+			COMPLETE_WITH("AGGREGATE", "COLLATION", "DOMAIN", "FUNCTION",
+						  "INDEX", "OPERATOR", "PROCEDURE", "SEQUENCE", "TABLE",
+						  "TEXT SEARCH CONFIGURATION", "TEXT SEARCH DICTIONARY",
+						  "TEXT SEARCH PARSER", "TEXT SEARCH TEMPLATE",
+						  "TRIGGER", "TYPE", "VIEW",
 			/* for INDEX and TABLE/SEQUENCE, respectively */
 						  "UNIQUE", "UNLOGGED");
 		else
@@ -3534,15 +3538,15 @@ match_previous_words(int pattern_id,
 	else if (Matches("CREATE", "DATABASE", MatchAny, "STRATEGY"))
 		COMPLETE_WITH("WAL_LOG", "FILE_COPY");
 
-	/* CREATE DOMAIN */
-	else if (Matches("CREATE", "DOMAIN", MatchAny))
+	/* CREATE DOMAIN --- is allowed inside CREATE SCHEMA, so use TailMatches */
+	else if (TailMatches("CREATE", "DOMAIN", MatchAny))
 		COMPLETE_WITH("AS");
-	else if (Matches("CREATE", "DOMAIN", MatchAny, "AS"))
+	else if (TailMatches("CREATE", "DOMAIN", MatchAny, "AS"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes);
-	else if (Matches("CREATE", "DOMAIN", MatchAny, "AS", MatchAny))
+	else if (TailMatches("CREATE", "DOMAIN", MatchAny, "AS", MatchAny))
 		COMPLETE_WITH("COLLATE", "DEFAULT", "CONSTRAINT",
 					  "NOT NULL", "NULL", "CHECK (");
-	else if (Matches("CREATE", "DOMAIN", MatchAny, "COLLATE"))
+	else if (TailMatches("CREATE", "DOMAIN", MatchAny, "COLLATE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_collations);
 
 	/* CREATE EXTENSION */
@@ -3906,10 +3910,10 @@ match_previous_words(int pattern_id,
 	else if (Matches("CREATE", "TABLESPACE", MatchAny, "OWNER", MatchAny))
 		COMPLETE_WITH("LOCATION");
 
-/* CREATE TEXT SEARCH */
-	else if (Matches("CREATE", "TEXT", "SEARCH"))
+/* CREATE TEXT SEARCH --- is allowed inside CREATE SCHEMA, so use TailMatches */
+	else if (TailMatches("CREATE", "TEXT", "SEARCH"))
 		COMPLETE_WITH("CONFIGURATION", "DICTIONARY", "PARSER", "TEMPLATE");
-	else if (Matches("CREATE", "TEXT", "SEARCH", "CONFIGURATION|DICTIONARY|PARSER|TEMPLATE", MatchAny))
+	else if (TailMatches("CREATE", "TEXT", "SEARCH", "CONFIGURATION|DICTIONARY|PARSER|TEMPLATE", MatchAny))
 		COMPLETE_WITH("(");
 
 /* CREATE TRANSFORM */
diff --git a/src/fe_utils/psqlscan.l b/src/fe_utils/psqlscan.l
index 7e1f9b22c49..d29dda4d8e1 100644
--- a/src/fe_utils/psqlscan.l
+++ b/src/fe_utils/psqlscan.l
@@ -928,19 +928,24 @@ other			.
 					 * writing a full parser here, the following heuristic
 					 * should work.  First, we track whether the beginning of
 					 * the statement matches CREATE [OR REPLACE]
-					 * {FUNCTION|PROCEDURE}
+					 * {FUNCTION|PROCEDURE|SCHEMA}.  (Allowing this in
+					 * CREATE SCHEMA, without tracking whether we're within a
+					 * CREATE FUNCTION/PROCEDURE subcommand, is a bit shaky
+					 * but should be okay with the present set of valid
+					 * subcommands.)
 					 */
 
 					if (cur_state->identifier_count == 0)
 						memset(cur_state->identifiers, 0, sizeof(cur_state->identifiers));
 
-					if (pg_strcasecmp(yytext, "create") == 0 ||
-						pg_strcasecmp(yytext, "function") == 0 ||
-						pg_strcasecmp(yytext, "procedure") == 0 ||
-						pg_strcasecmp(yytext, "or") == 0 ||
-						pg_strcasecmp(yytext, "replace") == 0)
+					if (cur_state->identifier_count < sizeof(cur_state->identifiers))
 					{
-						if (cur_state->identifier_count < sizeof(cur_state->identifiers))
+						if (pg_strcasecmp(yytext, "create") == 0 ||
+							pg_strcasecmp(yytext, "function") == 0 ||
+							pg_strcasecmp(yytext, "procedure") == 0 ||
+							pg_strcasecmp(yytext, "or") == 0 ||
+							pg_strcasecmp(yytext, "replace") == 0 ||
+							pg_strcasecmp(yytext, "schema") == 0)
 							cur_state->identifiers[cur_state->identifier_count] = pg_tolower((unsigned char) yytext[0]);
 					}
 
@@ -949,7 +954,8 @@ other			.
 					if (cur_state->identifiers[0] == 'c' &&
 						(cur_state->identifiers[1] == 'f' || cur_state->identifiers[1] == 'p' ||
 						 (cur_state->identifiers[1] == 'o' && cur_state->identifiers[2] == 'r' &&
-						  (cur_state->identifiers[3] == 'f' || cur_state->identifiers[3] == 'p'))) &&
+						  (cur_state->identifiers[3] == 'f' || cur_state->identifiers[3] == 'p')) ||
+						 cur_state->identifiers[1] == 's') &&
 						cur_state->paren_depth == 0)
 					{
 						if (pg_strcasecmp(yytext, "begin") == 0)
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 6ed85ef7446..a867786899b 100644
--- a/src/test/modules/test_ddl_deparse/expected/create_schema.out
+++ b/src/test/modules/test_ddl_deparse/expected/create_schema.out
@@ -13,10 +13,46 @@ CREATE SCHEMA IF NOT EXISTS baz;
 NOTICE:  schema "baz" already exists, skipping
 CREATE SCHEMA element_test
   CREATE TABLE foo (id int)
-  CREATE VIEW bar AS SELECT * FROM foo;
+  CREATE VIEW bar AS SELECT * FROM foo
+  CREATE COLLATION coll (LOCALE="C")
+  CREATE DOMAIN d1 AS INT
+  CREATE FUNCTION et_add(int4, int4) RETURNS int4 LANGUAGE sql
+    AS 'SELECT $1 + $2'
+  CREATE PROCEDURE et_proc(int4, int4)
+    BEGIN ATOMIC SELECT et_add($1,$2); END
+  CREATE TYPE floatrange AS RANGE (subtype = float8, subtype_diff = float8mi)
+  CREATE TYPE ss AS (a int)
+  CREATE TYPE sss
+  CREATE TYPE rainbow AS ENUM ('red', 'orange')
+  CREATE TEXT SEARCH PARSER et_ts_prs
+    (start = prsd_start, gettoken = prsd_nexttoken, end = prsd_end,
+     lextypes = prsd_lextype)
+;
 NOTICE:  DDL test: type simple, tag CREATE SCHEMA
 NOTICE:  DDL test: type simple, tag CREATE TABLE
 NOTICE:  DDL test: type simple, tag CREATE VIEW
+NOTICE:  DDL test: type simple, tag CREATE COLLATION
+NOTICE:  DDL test: type simple, tag CREATE DOMAIN
+NOTICE:  DDL test: type simple, tag CREATE FUNCTION
+NOTICE:  DDL test: type simple, tag CREATE PROCEDURE
+NOTICE:  DDL test: type simple, tag CREATE TYPE
+NOTICE:  DDL test: type simple, tag CREATE TYPE
+NOTICE:  DDL test: type simple, tag CREATE TYPE
+NOTICE:  DDL test: type simple, tag CREATE TYPE
+NOTICE:  DDL test: type simple, tag CREATE TEXT SEARCH PARSER
+DROP SCHEMA element_test CASCADE;
+NOTICE:  drop cascades to 11 other objects
+DETAIL:  drop cascades to table element_test.foo
+drop cascades to view element_test.bar
+drop cascades to collation element_test.coll
+drop cascades to type element_test.d1
+drop cascades to function element_test.et_add(integer,integer)
+drop cascades to function element_test.et_proc(integer,integer)
+drop cascades to type element_test.floatrange
+drop cascades to type element_test.ss
+drop cascades to type element_test.sss
+drop cascades to type element_test.rainbow
+drop cascades to text search parser element_test.et_ts_prs
 CREATE SCHEMA regress_schema_1
 CREATE TABLE t4(
     b INT,
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 145aef2a75a..7ba641d06d6 100644
--- a/src/test/modules/test_ddl_deparse/sql/create_schema.sql
+++ b/src/test/modules/test_ddl_deparse/sql/create_schema.sql
@@ -14,7 +14,23 @@ CREATE SCHEMA IF NOT EXISTS baz;
 
 CREATE SCHEMA element_test
   CREATE TABLE foo (id int)
-  CREATE VIEW bar AS SELECT * FROM foo;
+  CREATE VIEW bar AS SELECT * FROM foo
+  CREATE COLLATION coll (LOCALE="C")
+  CREATE DOMAIN d1 AS INT
+  CREATE FUNCTION et_add(int4, int4) RETURNS int4 LANGUAGE sql
+    AS 'SELECT $1 + $2'
+  CREATE PROCEDURE et_proc(int4, int4)
+    BEGIN ATOMIC SELECT et_add($1,$2); END
+  CREATE TYPE floatrange AS RANGE (subtype = float8, subtype_diff = float8mi)
+  CREATE TYPE ss AS (a int)
+  CREATE TYPE sss
+  CREATE TYPE rainbow AS ENUM ('red', 'orange')
+  CREATE TEXT SEARCH PARSER et_ts_prs
+    (start = prsd_start, gettoken = prsd_nexttoken, end = prsd_end,
+     lextypes = prsd_lextype)
+;
+
+DROP SCHEMA element_test CASCADE;
 
 CREATE SCHEMA regress_schema_1
 CREATE TABLE t4(
diff --git a/src/test/regress/expected/create_schema.out b/src/test/regress/expected/create_schema.out
index b34b9988962..98dad2526a9 100644
--- a/src/test/regress/expected/create_schema.out
+++ b/src/test/regress/expected/create_schema.out
@@ -177,5 +177,140 @@ 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
+-- Test miscellaneous object types within CREATE SCHEMA.
+CREATE SCHEMA regress_schema_misc
+  CREATE AGGREGATE cs_sum(int4)
+    (
+        SFUNC = int4_sum(int8, int4),
+        STYPE = int8,
+        INITCOND = '0'
+    )
+  CREATE COLLATION cs_builtin_c ( PROVIDER = builtin, LOCALE = "C" )
+  CREATE DOMAIN cs_positive AS integer CHECK (VALUE > 0)
+  CREATE FUNCTION cs_add(int4, int4) returns int4 language sql
+    as 'select $1 + $2'
+  CREATE OPERATOR + (function = cs_add, leftarg = int4, rightarg = int4)
+  CREATE PROCEDURE cs_proc(int4, int4)
+    BEGIN ATOMIC SELECT cs_add($1,$2); END
+  CREATE TEXT SEARCH CONFIGURATION cs_ts_conf (copy=english)
+  CREATE TEXT SEARCH DICTIONARY cs_ts_dict (template=simple)
+  CREATE TEXT SEARCH PARSER cs_ts_prs
+    (start = prsd_start, gettoken = prsd_nexttoken, end = prsd_end,
+     lextypes = prsd_lextype)
+  CREATE TEXT SEARCH TEMPLATE cs_ts_temp (lexize=dsimple_lexize)
+  CREATE TYPE regress_schema_misc.cs_enum AS ENUM ('red', 'orange')
+  CREATE TYPE cs_composite AS (a int, b float8)
+  CREATE TYPE cs_range AS RANGE (subtype = float8, subtype_diff = float8mi)
+  -- demonstrate creation of a base type with its I/O functions
+  CREATE TYPE cs_type
+  CREATE FUNCTION cs_type_in(cstring)
+    RETURNS cs_type LANGUAGE internal IMMUTABLE PARALLEL SAFE STRICT
+    AS 'int4in'
+  CREATE FUNCTION cs_type_out(cs_type)
+    RETURNS cstring LANGUAGE internal IMMUTABLE PARALLEL SAFE STRICT
+    AS 'int4out'
+  CREATE TYPE cs_type (
+    INPUT = cs_type_in,
+    OUTPUT = cs_type_out,
+    LIKE = int4
+  )
+;
+NOTICE:  return type cs_type is only a shell
+NOTICE:  argument type cs_type is only a shell
+LINE 29:   CREATE FUNCTION cs_type_out(cs_type)
+                                       ^
+\df regress_schema_misc.cs_add
+                              List of functions
+       Schema        |  Name  | Result data type | Argument data types | Type 
+---------------------+--------+------------------+---------------------+------
+ regress_schema_misc | cs_add | integer          | integer, integer    | func
+(1 row)
+
+\df regress_schema_misc.cs_proc
+                                List of functions
+       Schema        |  Name   | Result data type |  Argument data types   | Type 
+---------------------+---------+------------------+------------------------+------
+ regress_schema_misc | cs_proc |                  | IN integer, IN integer | proc
+(1 row)
+
+\da regress_schema_misc.cs_sum
+                             List of aggregate functions
+       Schema        |  Name  | Result data type | Argument data types | Description 
+---------------------+--------+------------------+---------------------+-------------
+ regress_schema_misc | cs_sum | bigint           | integer             | 
+(1 row)
+
+\do regress_schema_misc.+
+                                    List of operators
+       Schema        | Name | Left arg type | Right arg type | Result type | Description 
+---------------------+------+---------------+----------------+-------------+-------------
+ regress_schema_misc | +    | integer       | integer        | integer     | 
+(1 row)
+
+\dO regress_schema_misc.*
+                                          List of collations
+       Schema        |     Name     | Provider | Collate | Ctype | Locale | ICU Rules | Deterministic? 
+---------------------+--------------+----------+---------+-------+--------+-----------+----------------
+ regress_schema_misc | cs_builtin_c | builtin  |         |       | C      |           | yes
+(1 row)
+
+\dT regress_schema_misc.*
+                          List of data types
+       Schema        |               Name                | Description 
+---------------------+-----------------------------------+-------------
+ regress_schema_misc | regress_schema_misc.cs_composite  | 
+ regress_schema_misc | regress_schema_misc.cs_enum       | 
+ regress_schema_misc | regress_schema_misc.cs_multirange | 
+ regress_schema_misc | regress_schema_misc.cs_positive   | 
+ regress_schema_misc | regress_schema_misc.cs_range      | 
+ regress_schema_misc | regress_schema_misc.cs_type       | 
+(6 rows)
+
+\dF regress_schema_misc.*
+       List of text search configurations
+       Schema        |    Name    | Description 
+---------------------+------------+-------------
+ regress_schema_misc | cs_ts_conf | 
+(1 row)
+
+\dFd regress_schema_misc.*
+        List of text search dictionaries
+       Schema        |    Name    | Description 
+---------------------+------------+-------------
+ regress_schema_misc | cs_ts_dict | 
+(1 row)
+
+\dFp regress_schema_misc.*
+          List of text search parsers
+       Schema        |   Name    | Description 
+---------------------+-----------+-------------
+ regress_schema_misc | cs_ts_prs | 
+(1 row)
+
+\dFt regress_schema_misc.*
+         List of text search templates
+       Schema        |    Name    | Description 
+---------------------+------------+-------------
+ regress_schema_misc | cs_ts_temp | 
+(1 row)
+
+DROP SCHEMA regress_schema_misc CASCADE;
+NOTICE:  drop cascades to 16 other objects
+DETAIL:  drop cascades to function regress_schema_misc.cs_sum(integer)
+drop cascades to collation regress_schema_misc.cs_builtin_c
+drop cascades to type regress_schema_misc.cs_positive
+drop cascades to function regress_schema_misc.cs_add(integer,integer)
+drop cascades to operator regress_schema_misc.+(integer,integer)
+drop cascades to function regress_schema_misc.cs_proc(integer,integer)
+drop cascades to text search configuration regress_schema_misc.cs_ts_conf
+drop cascades to text search dictionary regress_schema_misc.cs_ts_dict
+drop cascades to text search parser regress_schema_misc.cs_ts_prs
+drop cascades to text search template regress_schema_misc.cs_ts_temp
+drop cascades to type regress_schema_misc.cs_enum
+drop cascades to type regress_schema_misc.cs_composite
+drop cascades to type regress_schema_misc.cs_range
+drop cascades to function regress_schema_misc.cs_type_out(regress_schema_misc.cs_type)
+drop cascades to type regress_schema_misc.cs_type
+drop cascades to function regress_schema_misc.cs_type_in(cstring)
 -- Clean up
 DROP ROLE regress_create_schema_role;
diff --git a/src/test/regress/sql/create_schema.sql b/src/test/regress/sql/create_schema.sql
index 0f2accc59ec..08aebc8a5a1 100644
--- a/src/test/regress/sql/create_schema.sql
+++ b/src/test/regress/sql/create_schema.sql
@@ -98,5 +98,74 @@ CREATE SCHEMA regress_schema_fk
 
 DROP SCHEMA regress_schema_fk CASCADE;
 
+-- Test miscellaneous object types within CREATE SCHEMA.
+CREATE SCHEMA regress_schema_misc
+  CREATE AGGREGATE cs_sum(int4)
+    (
+        SFUNC = int4_sum(int8, int4),
+        STYPE = int8,
+        INITCOND = '0'
+    )
+
+  CREATE COLLATION cs_builtin_c ( PROVIDER = builtin, LOCALE = "C" )
+
+  CREATE DOMAIN cs_positive AS integer CHECK (VALUE > 0)
+
+  CREATE FUNCTION cs_add(int4, int4) returns int4 language sql
+    as 'select $1 + $2'
+
+  CREATE OPERATOR + (function = cs_add, leftarg = int4, rightarg = int4)
+
+  CREATE PROCEDURE cs_proc(int4, int4)
+    BEGIN ATOMIC SELECT cs_add($1,$2); END
+
+  CREATE TEXT SEARCH CONFIGURATION cs_ts_conf (copy=english)
+
+  CREATE TEXT SEARCH DICTIONARY cs_ts_dict (template=simple)
+
+  CREATE TEXT SEARCH PARSER cs_ts_prs
+    (start = prsd_start, gettoken = prsd_nexttoken, end = prsd_end,
+     lextypes = prsd_lextype)
+
+  CREATE TEXT SEARCH TEMPLATE cs_ts_temp (lexize=dsimple_lexize)
+
+  CREATE TYPE regress_schema_misc.cs_enum AS ENUM ('red', 'orange')
+
+  CREATE TYPE cs_composite AS (a int, b float8)
+
+  CREATE TYPE cs_range AS RANGE (subtype = float8, subtype_diff = float8mi)
+
+  -- demonstrate creation of a base type with its I/O functions
+
+  CREATE TYPE cs_type
+
+  CREATE FUNCTION cs_type_in(cstring)
+    RETURNS cs_type LANGUAGE internal IMMUTABLE PARALLEL SAFE STRICT
+    AS 'int4in'
+
+  CREATE FUNCTION cs_type_out(cs_type)
+    RETURNS cstring LANGUAGE internal IMMUTABLE PARALLEL SAFE STRICT
+    AS 'int4out'
+
+  CREATE TYPE cs_type (
+    INPUT = cs_type_in,
+    OUTPUT = cs_type_out,
+    LIKE = int4
+  )
+;
+
+\df regress_schema_misc.cs_add
+\df regress_schema_misc.cs_proc
+\da regress_schema_misc.cs_sum
+\do regress_schema_misc.+
+\dO regress_schema_misc.*
+\dT regress_schema_misc.*
+\dF regress_schema_misc.*
+\dFd regress_schema_misc.*
+\dFp regress_schema_misc.*
+\dFt regress_schema_misc.*
+
+DROP SCHEMA regress_schema_misc CASCADE;
+
 -- Clean up
 DROP ROLE regress_create_schema_role;
-- 
2.43.7



^ permalink  raw  reply  [nested|flat] 32+ messages in thread

* Re: CREATE SCHEMA ... CREATE DOMAIN support
  2024-11-30 19:08 Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  2024-12-03 19:35 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Peter Eisentraut <[email protected]>
  2024-12-03 20:07   ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  2024-12-11 06:22     ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <[email protected]>
  2024-12-11 17:08       ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  2025-08-19 03:36         ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <[email protected]>
  2025-08-22 08:59           ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <[email protected]>
  2025-09-02 21:24             ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  2025-12-17 13:29               ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <[email protected]>
  2026-04-03 18:35                 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  2026-04-05 18:42                   ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  2026-04-05 20:15                     ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
@ 2026-04-06 02:29                       ` jian he <[email protected]>
  2026-04-06 03:04                         ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  1 sibling, 1 reply; 32+ messages in thread

From: jian he @ 2026-04-06 02:29 UTC (permalink / raw)
  To: Tom Lane <[email protected]>; +Cc: Kirill Reshke <[email protected]>; Peter Eisentraut <[email protected]>; PostgreSQL Hackers <[email protected]>

On Mon, Apr 6, 2026 at 4:15 AM Tom Lane <[email protected]> wrote:
>
> Here's a revised patchset that I think is pretty close to committable.
> I reorganized it some compared to v11: 0001 is still about the same,
> but the business with allowing circular foreign keys is now in 0002,
> because it seems like that bears directly on whether we should
> consider 0001 acceptable.  And then I squashed all the object-type
> additions into 0003, because dealing with them all at once seemed
> simpler.
>
> I rewrote 0002 rather heavily, mainly because it assumed it could
> scribble on the input query tree which I don't think is okay.
> The functionality is still the same though.
>

transformCreateSchemaStmtElements you mentioned

 * 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.

But there is still no explanation about why it's not ok to scribble on
the input query tree.
V13-0002 looks great, and it is way more intuitive than my approach!
Thanks!

+ DeconstructQualifiedName(qualified_name, &obj_schema, &obj_name);
+ if (obj_schema != NULL &&
+ strcmp(context_schema, obj_schema) != 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_SCHEMA_DEFINITION),
+ errmsg("CREATE specifies a schema (%s) "
+ "different from the one being created (%s)",
+ obj_schema, context_schema)));

There is no tests for the above. It would be better to have one dummy
test verify the error message.

it would be better change to
+ errmsg("CREATE specifies a schema (%s) different from the one being
created (%s)",
It's better for regex pattern searching, for new line you need to know
how to escape.

+ (errcode(ERRCODE_INVALID_SCHEMA_DEFINITION),
The leading parenthesis is not needed.

> I was kind of sad that 0003 didn't support functions/procedures,
> because (a) the SQL spec requires those in CREATE SCHEMA, and
> (b) they are a flagship feature for Postgres, mainly because they
> are such a critical aspect of extendability.  So I added that,
> which only required adding CreateFunctionStmt to the set of
> supported node types in transformCreateSchemaStmtElements.
>

I guess at the time, I wasn't aggressive enough to consider CREATE
SCHEMA CREATE FUNCTION.

I saw the "Author" line in commit messages v13-0002 and v13-0003.
You should be added as an author on both v13-0002 and v13-0003.
Your changes simplified 0002 a lot, and you added the other CREATE
SCHEMA subcommands to 0003.

Except for the above issue, v13 all looks good to me.



--
jian
https://www.enterprisedb.com/





^ permalink  raw  reply  [nested|flat] 32+ messages in thread

* Re: CREATE SCHEMA ... CREATE DOMAIN support
  2024-11-30 19:08 Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  2024-12-03 19:35 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Peter Eisentraut <[email protected]>
  2024-12-03 20:07   ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  2024-12-11 06:22     ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <[email protected]>
  2024-12-11 17:08       ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  2025-08-19 03:36         ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <[email protected]>
  2025-08-22 08:59           ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <[email protected]>
  2025-09-02 21:24             ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  2025-12-17 13:29               ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <[email protected]>
  2026-04-03 18:35                 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  2026-04-05 18:42                   ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  2026-04-05 20:15                     ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  2026-04-06 02:29                       ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <[email protected]>
@ 2026-04-06 03:04                         ` Tom Lane <[email protected]>
  2026-04-06 19:19                           ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  0 siblings, 1 reply; 32+ messages in thread

From: Tom Lane @ 2026-04-06 03:04 UTC (permalink / raw)
  To: jian he <[email protected]>; +Cc: Kirill Reshke <[email protected]>; Peter Eisentraut <[email protected]>; PostgreSQL Hackers <[email protected]>

jian he <[email protected]> writes:
> On Mon, Apr 6, 2026 at 4:15 AM Tom Lane <[email protected]> wrote:
>> I rewrote 0002 rather heavily, mainly because it assumed it could
>> scribble on the input query tree which I don't think is okay.

> But there is still no explanation about why it's not ok to scribble on
> the input query tree.

Because it might be in a cached plan tree and thus subject to being
re-used.  Now, you could sort of get away with that as long as
whatever changes you make are idempotent.  But palloc'ing new nodes
in the current memory context and then putting links to them into the
possibly-longer-lived source tree will not work.  On the whole I think
it'd be too fragile.

See also [1].  I didn't get a chance to make that happen during v19,
but it's still on the radar, and once it happens this sort of shortcut
would definitely fail.

> it would be better change to
> + errmsg("CREATE specifies a schema (%s) different from the one being
> created (%s)",
> It's better for regex pattern searching, for new line you need to know
> how to escape.

I made it look the same as the existing copy of the message.
I'm not too worried about the line break where it is; I doubt
anyone would be doing a code search that would fail because
of that.  Breaking in the middle of either phrase of the
message would be bad, agreed.

			regards, tom lane

[1] https://www.postgresql.org/message-id/2531459.1743871597%40sss.pgh.pa.us





^ permalink  raw  reply  [nested|flat] 32+ messages in thread

* Re: CREATE SCHEMA ... CREATE DOMAIN support
  2024-11-30 19:08 Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  2024-12-03 19:35 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Peter Eisentraut <[email protected]>
  2024-12-03 20:07   ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  2024-12-11 06:22     ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <[email protected]>
  2024-12-11 17:08       ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  2025-08-19 03:36         ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <[email protected]>
  2025-08-22 08:59           ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <[email protected]>
  2025-09-02 21:24             ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  2025-12-17 13:29               ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <[email protected]>
  2026-04-03 18:35                 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  2026-04-05 18:42                   ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  2026-04-05 20:15                     ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
  2026-04-06 02:29                       ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <[email protected]>
  2026-04-06 03:04                         ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
@ 2026-04-06 19:19                           ` Tom Lane <[email protected]>
  0 siblings, 0 replies; 32+ messages in thread

From: Tom Lane @ 2026-04-06 19:19 UTC (permalink / raw)
  To: jian he <[email protected]>; +Cc: Kirill Reshke <[email protected]>; Peter Eisentraut <[email protected]>; PostgreSQL Hackers <[email protected]>

I pushed v13 after a tiny bit of additional tweaking.

			regards, tom lane





^ permalink  raw  reply  [nested|flat] 32+ messages in thread


end of thread, other threads:[~2026-04-06 19:19 UTC | newest]

Thread overview: 32+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2024-11-30 19:08 Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <[email protected]>
2024-11-30 23:33 ` Tom Lane <[email protected]>
2024-12-01 05:39   ` Kirill Reshke <[email protected]>
2024-12-01 05:53     ` Tom Lane <[email protected]>
2024-12-01 09:59       ` jian he <[email protected]>
2024-12-01 16:57         ` Tom Lane <[email protected]>
2024-12-01 22:30           ` Tom Lane <[email protected]>
2024-12-02 01:34             ` Michael Paquier <[email protected]>
2024-12-02 02:15               ` Tom Lane <[email protected]>
2024-12-02 16:08                 ` Vik Fearing <[email protected]>
2024-12-02 16:56                   ` Tom Lane <[email protected]>
2024-12-02 17:26                     ` Vik Fearing <[email protected]>
2024-12-03 19:35 ` Peter Eisentraut <[email protected]>
2024-12-03 20:07   ` Tom Lane <[email protected]>
2024-12-11 06:22     ` Kirill Reshke <[email protected]>
2024-12-11 17:08       ` Tom Lane <[email protected]>
2025-08-19 03:36         ` jian he <[email protected]>
2025-08-22 08:59           ` Kirill Reshke <[email protected]>
2025-08-26 04:53             ` jian he <[email protected]>
2025-09-02 21:24             ` Tom Lane <[email protected]>
2025-12-17 13:29               ` jian he <[email protected]>
2025-12-31 01:40                 ` jian he <[email protected]>
2026-01-19 15:06                   ` jian he <[email protected]>
2026-03-10 14:11                     ` jian he <[email protected]>
2026-03-30 02:44                       ` jian he <[email protected]>
2026-04-03 18:35                 ` Tom Lane <[email protected]>
2026-04-05 18:42                   ` Tom Lane <[email protected]>
2026-04-05 20:15                     ` Tom Lane <[email protected]>
2026-04-05 22:05                       ` Tom Lane <[email protected]>
2026-04-06 02:29                       ` jian he <[email protected]>
2026-04-06 03:04                         ` Tom Lane <[email protected]>
2026-04-06 19:19                           ` Tom Lane <[email protected]>

This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox