From 1a255ed67ae9a020c20ad0c3297b91e40ec7ddd0 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
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

