pg.ddx.io pgsql-hackers@postgresql.org mailing list archive
help / color / mirror / Atom feedCREATE SCHEMA ... CREATE DOMAIN support
55+ messages / 9 participants
[nested] [flat]
* CREATE SCHEMA ... CREATE DOMAIN support
@ 2024-11-12 12:54 Kirill Reshke <reshkekirill@gmail.com>
2024-11-23 05:19 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
0 siblings, 1 reply; 55+ messages in thread
From: Kirill Reshke @ 2024-11-12 12:54 UTC (permalink / raw)
To: PostgreSQL Hackers <pgsql-hackers@lists.postgresql.org>
Hi hackers!
This copy of my reply mail from pgsql-general[0], & [1] which was held
for moderation for some reason.
Here it goes as-is :
== begin
Hi Álvaro, thanks for the detailed explanation.
So, IIUC you are suggesting to support SQL standard features before
any work with PostgreSQL extension.
Ok, I will try to go this way. PFA patch implementing CREATE DOMAIN
support for CREATE SCHEMA statement.
Of all other options, CREATE DOMAIN support looks like the most stranfoward one.
Patch obviously leaks doc & regression tests, but I'm posting it to
see if this contribution is needed in PostgreSQL
== end
[0] https://www.postgresql.org/message-id/CALdSSPgxcRkooZ2iQ5A7XhYoexVAdbiT6znZDqJTE8hxUVjz_A%40mail.gma...
[1] https://www.postgresql.org/message-id/202411111009.ckna4vp7ahyk%40alvherre.pgsql
--
Best regards,
Kirill Reshke
Attachments:
[application/octet-stream] v1-0001-Extend-CREATE-SCHEMA-element-with-DOMAIN-support.patch (2.3K, ../../CALdSSPh4jUSDsWu3K58hjO60wnTRR0DuO4CKRcwa8EVuOSfXxg@mail.gmail.com/2-v1-0001-Extend-CREATE-SCHEMA-element-with-DOMAIN-support.patch)
download | inline diff:
From 86515dab036e9a8339d250f8da3a6185d3bc0ff2 Mon Sep 17 00:00:00 2001
From: reshke kirill <reshke@double.cloud>
Date: Mon, 11 Nov 2024 21:18:56 +0000
Subject: [PATCH v1] Extend CREATE SCHEMA element with DOMAIN support.
SQL standart allow domain to be specified with CREATE SCHEMA
statement. This patch adds support in PostgreSQL for that.
---
src/backend/parser/gram.y | 1 +
src/backend/parser/parse_utilcmd.c | 16 ++++++++++++++++
2 files changed, 17 insertions(+)
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 67eb96396a..ad8d9270ac 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -1584,6 +1584,7 @@ schema_stmt:
| CreateTrigStmt
| GrantStmt
| ViewStmt
+ | CreateDomainStmt
;
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 0f324ee4e3..e74b6f8a04 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -105,6 +105,7 @@ typedef struct
List *indexes; /* CREATE INDEX items */
List *triggers; /* CREATE TRIGGER items */
List *grants; /* GRANT items */
+ List *domains; /* DOMAIN items */
} CreateSchemaStmtContext;
@@ -4039,6 +4040,7 @@ transformCreateSchemaStmtElements(List *schemaElts, const char *schemaName)
cxt.indexes = NIL;
cxt.triggers = NIL;
cxt.grants = NIL;
+ cxt.domains = NIL;
/*
* Run through each schema element in the schema element list. Separate
@@ -4107,6 +4109,19 @@ transformCreateSchemaStmtElements(List *schemaElts, const char *schemaName)
cxt.grants = lappend(cxt.grants, element);
break;
+ case T_CreateDomainStmt:
+ {
+ CreateDomainStmt *elp = (CreateDomainStmt *) element;
+
+ /* DOMAIN name can be already qualified. */
+ if (elp->domainname->length == 1) {
+ elp->domainname = lcons(makeString(pstrdup(cxt.schemaname)), elp->domainname);
+ }
+
+ cxt.domains = lappend(cxt.domains, element);
+ }
+ break;
+
default:
elog(ERROR, "unrecognized node type: %d",
(int) nodeTag(element));
@@ -4120,6 +4135,7 @@ transformCreateSchemaStmtElements(List *schemaElts, const char *schemaName)
result = list_concat(result, cxt.indexes);
result = list_concat(result, cxt.triggers);
result = list_concat(result, cxt.grants);
+ result = list_concat(result, cxt.domains);
return result;
}
--
2.34.1
^ permalink raw reply [nested|flat] 55+ messages in thread
* Re: CREATE SCHEMA ... CREATE DOMAIN support
2024-11-12 12:54 CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
@ 2024-11-23 05:19 ` jian he <jian.universality@gmail.com>
2024-11-27 03:42 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
0 siblings, 1 reply; 55+ messages in thread
From: jian he @ 2024-11-23 05:19 UTC (permalink / raw)
To: Kirill Reshke <reshkekirill@gmail.com>; +Cc: PostgreSQL Hackers <pgsql-hackers@lists.postgresql.org>
On Tue, Nov 12, 2024 at 8:55 PM Kirill Reshke <reshkekirill@gmail.com> wrote:
>
> Patch obviously leaks doc & regression tests, but I'm posting it to
> see if this contribution is needed in PostgreSQL
the following two statement should fail:
CREATE SCHEMA regress_schema_2 AUTHORIZATION CURRENT_ROLE
CREATE table t(a ss)
create domain public.ss as text not null default 'hello' constraint
nn check (value <> 'hello');
CREATE SCHEMA regress_schema_2 AUTHORIZATION CURRENT_ROLE
CREATE table t(a ss)
create domain postgres.public.ss as text not null default 'hello'
constraint nn check (value <> 'hello');
we aslo need to consider the dependency issue. like the following should be ok.
CREATE SCHEMA regress_schema_3 AUTHORIZATION CURRENT_ROLE
create view test as select 'hello'::ss as test
CREATE table t(a ss)
create domain ss as text not null;
i fixed these two issues, and add the above example as tests in
src/test/regress/sql/create_schema.sql
I didn't add a doc entry. I will do it later.
Attachments:
[text/x-patch] v2-0001-support-CREATE-SCHEMA-.-CREATE-DOMAIN.patch (7.5K, ../../CACJufxG+mrh2O9RS0gX43gU6sv+CMY847eMjMQpe8t4ou-2ryg@mail.gmail.com/2-v2-0001-support-CREATE-SCHEMA-.-CREATE-DOMAIN.patch)
download | inline diff:
From aa1d229544636453fddbb0ba01ddb12bd515f55e Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Sat, 23 Nov 2024 13:13:57 +0800
Subject: [PATCH v2 1/1] support CREATE SCHEMA ... CREATE DOMAIN
now you can:
CREATE SCHEMA regress_schema_3 AUTHORIZATION CURRENT_ROLE
create view test as select 'hello'::ss as test
CREATE table t(a ss)
create domain ss as text not null;
the domain will be created in the same schema as the created schema.
it's not allowed to let domain created in different schema as the the created schema.
discussion: https://postgr.es/m/CALdSSPh4jUSDsWu3K58hjO60wnTRR0DuO4CKRcwa8EVuOSfXxg%40mail.gmail.com
---
src/backend/parser/gram.y | 1 +
src/backend/parser/parse_utilcmd.c | 26 +++++++++++++
src/test/regress/expected/create_schema.out | 43 +++++++++++++++++++++
src/test/regress/sql/create_schema.sql | 26 +++++++++++++
4 files changed, 96 insertions(+)
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 67eb96396a..ad8d9270ac 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -1584,6 +1584,7 @@ schema_stmt:
| CreateTrigStmt
| GrantStmt
| ViewStmt
+ | CreateDomainStmt
;
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 0f324ee4e3..e5f5cd22ac 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -105,6 +105,7 @@ typedef struct
List *indexes; /* CREATE INDEX items */
List *triggers; /* CREATE TRIGGER items */
List *grants; /* GRANT items */
+ List *domains; /* DOMAIN items */
} CreateSchemaStmtContext;
@@ -4039,6 +4040,7 @@ transformCreateSchemaStmtElements(List *schemaElts, const char *schemaName)
cxt.indexes = NIL;
cxt.triggers = NIL;
cxt.grants = NIL;
+ cxt.domains = NIL;
/*
* Run through each schema element in the schema element list. Separate
@@ -4107,6 +4109,29 @@ transformCreateSchemaStmtElements(List *schemaElts, const char *schemaName)
cxt.grants = lappend(cxt.grants, element);
break;
+ case T_CreateDomainStmt:
+ {
+ CreateDomainStmt *elp = (CreateDomainStmt *) element;
+ char *domain_schema = NULL;
+
+ /* DOMAIN's schema must the same as the to be created schema
+ * if length of domainname > 3 will fail at
+ * DeconstructQualifiedName,
+ */
+ if (list_length(elp->domainname) == 2)
+ {
+ domain_schema = strVal(list_nth(elp->domainname, 0));
+ setSchemaName(cxt.schemaname, &domain_schema);
+ }
+ else if (list_length(elp->domainname) == 3)
+ {
+ domain_schema = strVal(list_nth(elp->domainname, 1));
+ setSchemaName(cxt.schemaname, &domain_schema);
+ }
+ cxt.domains = lappend(cxt.domains, element);
+ }
+ break;
+
default:
elog(ERROR, "unrecognized node type: %d",
(int) nodeTag(element));
@@ -4114,6 +4139,7 @@ transformCreateSchemaStmtElements(List *schemaElts, const char *schemaName)
}
result = NIL;
+ result = list_concat(result, cxt.domains);
result = list_concat(result, cxt.sequences);
result = list_concat(result, cxt.tables);
result = list_concat(result, cxt.views);
diff --git a/src/test/regress/expected/create_schema.out b/src/test/regress/expected/create_schema.out
index 93302a07ef..cf99e8313f 100644
--- a/src/test/regress/expected/create_schema.out
+++ b/src/test/regress/expected/create_schema.out
@@ -58,6 +58,49 @@ CREATE SCHEMA regress_schema_1 AUTHORIZATION CURRENT_ROLE
EXECUTE FUNCTION schema_trig.no_func();
ERROR: CREATE specifies a schema (schema_not_existing) different from the one being created (regress_schema_1)
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 table t(a ss)
+ create domain public.ss as text not null default 'hello' constraint nn check (value <> 'hello');
+ERROR: CREATE 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 table t(a ss)
+ create domain postgres.public.ss as text not null default 'hello' constraint nn check (value <> 'hello');
+ERROR: CREATE specifies a schema (public) different from the one being created (regress_schema_2)
+--ok, qualified schema name for domain should be same as the created schema.
+CREATE SCHEMA regress_schema_2 AUTHORIZATION CURRENT_ROLE
+ CREATE table t(a ss)
+ create domain regress_schema_2.ss as text not null;
+\dD regress_schema_2.*
+ List of domains
+ Schema | Name | Type | Collation | Nullable | Default | Check
+------------------+------+------+-----------+----------+---------+-------
+ regress_schema_2 | ss | text | | not null | |
+(1 row)
+
+--ok, no qualified schema name for domain. also check the dependency.
+CREATE SCHEMA regress_schema_3 AUTHORIZATION CURRENT_ROLE
+ create view test as select 'hello'::ss as test
+ CREATE table t(a ss)
+ create domain ss as text not null;
+\dD regress_schema_3.*
+ List of domains
+ Schema | Name | Type | Collation | Nullable | Default | Check
+------------------+------+------+-----------+----------+---------+-------
+ regress_schema_3 | ss | text | | not null | |
+(1 row)
+
+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 3 other objects
+DETAIL: drop cascades to type regress_schema_3.ss
+drop cascades to table regress_schema_3.t
+drop cascades to view regress_schema_3.test
-- 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/create_schema.sql b/src/test/regress/sql/create_schema.sql
index 1b7064247a..393a173ca8 100644
--- a/src/test/regress/sql/create_schema.sql
+++ b/src/test/regress/sql/create_schema.sql
@@ -47,6 +47,32 @@ CREATE SCHEMA regress_schema_1 AUTHORIZATION CURRENT_ROLE
EXECUTE FUNCTION schema_trig.no_func();
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 table t(a ss)
+ create domain public.ss as text not null default 'hello' constraint nn check (value <> 'hello');
+--fail. cannot create domain to other schema
+CREATE SCHEMA regress_schema_2 AUTHORIZATION CURRENT_ROLE
+ CREATE table t(a ss)
+ create domain postgres.public.ss as text not null default 'hello' constraint nn check (value <> 'hello');
+--ok, qualified schema name for domain should be same as the created schema.
+CREATE SCHEMA regress_schema_2 AUTHORIZATION CURRENT_ROLE
+ CREATE table t(a ss)
+ create domain regress_schema_2.ss as text not null;
+\dD regress_schema_2.*
+
+--ok, no qualified schema name for domain. also check the dependency.
+CREATE SCHEMA regress_schema_3 AUTHORIZATION CURRENT_ROLE
+ create view test as select 'hello'::ss as test
+ CREATE table t(a ss)
+ create domain ss as text not null;
+\dD regress_schema_3.*
+
+DROP SCHEMA regress_schema_2 CASCADE;
+DROP SCHEMA regress_schema_3 CASCADE;
+
-- Cases where the schema creation succeeds.
-- The schema created matches the role name.
CREATE SCHEMA AUTHORIZATION regress_create_schema_role
--
2.34.1
^ permalink raw reply [nested|flat] 55+ messages in thread
* Re: CREATE SCHEMA ... CREATE DOMAIN support
2024-11-12 12:54 CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-23 05:19 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
@ 2024-11-27 03:42 ` jian he <jian.universality@gmail.com>
2024-11-27 03:47 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-11-27 06:38 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
0 siblings, 2 replies; 55+ messages in thread
From: jian he @ 2024-11-27 03:42 UTC (permalink / raw)
To: Kirill Reshke <reshkekirill@gmail.com>; +Cc: PostgreSQL Hackers <pgsql-hackers@lists.postgresql.org>
On Sat, Nov 23, 2024 at 1:19 PM jian he <jian.universality@gmail.com> wrote:
>
> I didn't add a doc entry. I will do it later.
hi
attached patch with thorough tests and documentation.
one issue i still have is:
CREATE SCHEMA regress_schema_2 AUTHORIZATION CURRENT_ROLE
create domain ss1 as ss
create domain ss as text;
ERROR: type "ss" does not exist
the error message seems not that OK,
if we can point out the error position, that would be great.
like what we did with create schema create table:
CREATE SCHEMA regress_schema_2 AUTHORIZATION CURRENT_ROLE
create table t(a int, b x);
ERROR: type "x" does not exist
LINE 2: create table t(a int, b x);
^
Attachments:
[text/x-patch] v3-0001-support-CREATE-SCHEMA-.-CREATE-DOMAIN.patch (8.8K, ../../CACJufxFUdgqDiK9B+VNtnAwZOj=O3NqdLtXO_OrOwE5XPdCpBA@mail.gmail.com/2-v3-0001-support-CREATE-SCHEMA-.-CREATE-DOMAIN.patch)
download | inline diff:
From d95e70bdaa8b3b44d71507d656e3d7e1a3df2647 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Wed, 27 Nov 2024 11:27:40 +0800
Subject: [PATCH v3 1/1] support CREATE SCHEMA ... CREATE DOMAIN
now you can:
CREATE SCHEMA regress_schema_3 AUTHORIZATION CURRENT_ROLE
create view test as select 'hello'::ss as test
CREATE table t(a ss)
create domain ss as text not null;
the domain will be created within the to be created schema.
it's not allowed to let domain create within a different schema.
discussion: https://postgr.es/m/CALdSSPh4jUSDsWu3K58hjO60wnTRR0DuO4CKRcwa8EVuOSfXxg%40mail.gmail.com
---
doc/src/sgml/ref/create_schema.sgml | 2 +-
src/backend/parser/gram.y | 1 +
src/backend/parser/parse_utilcmd.c | 26 +++++++++++
src/test/regress/expected/create_schema.out | 51 +++++++++++++++++++++
src/test/regress/sql/create_schema.sql | 33 +++++++++++++
5 files changed, 112 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/create_schema.sgml b/doc/src/sgml/ref/create_schema.sgml
index ed69298ccc..06f6314a5b 100644
--- a/doc/src/sgml/ref/create_schema.sgml
+++ b/doc/src/sgml/ref/create_schema.sgml
@@ -100,7 +100,7 @@ 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
+ 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
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 67eb96396a..ad8d9270ac 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -1584,6 +1584,7 @@ schema_stmt:
| CreateTrigStmt
| GrantStmt
| ViewStmt
+ | CreateDomainStmt
;
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 0f324ee4e3..e5f5cd22ac 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -105,6 +105,7 @@ typedef struct
List *indexes; /* CREATE INDEX items */
List *triggers; /* CREATE TRIGGER items */
List *grants; /* GRANT items */
+ List *domains; /* DOMAIN items */
} CreateSchemaStmtContext;
@@ -4039,6 +4040,7 @@ transformCreateSchemaStmtElements(List *schemaElts, const char *schemaName)
cxt.indexes = NIL;
cxt.triggers = NIL;
cxt.grants = NIL;
+ cxt.domains = NIL;
/*
* Run through each schema element in the schema element list. Separate
@@ -4107,6 +4109,29 @@ transformCreateSchemaStmtElements(List *schemaElts, const char *schemaName)
cxt.grants = lappend(cxt.grants, element);
break;
+ case T_CreateDomainStmt:
+ {
+ CreateDomainStmt *elp = (CreateDomainStmt *) element;
+ char *domain_schema = NULL;
+
+ /* DOMAIN's schema must the same as the to be created schema
+ * if length of domainname > 3 will fail at
+ * DeconstructQualifiedName,
+ */
+ if (list_length(elp->domainname) == 2)
+ {
+ domain_schema = strVal(list_nth(elp->domainname, 0));
+ setSchemaName(cxt.schemaname, &domain_schema);
+ }
+ else if (list_length(elp->domainname) == 3)
+ {
+ domain_schema = strVal(list_nth(elp->domainname, 1));
+ setSchemaName(cxt.schemaname, &domain_schema);
+ }
+ cxt.domains = lappend(cxt.domains, element);
+ }
+ break;
+
default:
elog(ERROR, "unrecognized node type: %d",
(int) nodeTag(element));
@@ -4114,6 +4139,7 @@ transformCreateSchemaStmtElements(List *schemaElts, const char *schemaName)
}
result = NIL;
+ result = list_concat(result, cxt.domains);
result = list_concat(result, cxt.sequences);
result = list_concat(result, cxt.tables);
result = list_concat(result, cxt.views);
diff --git a/src/test/regress/expected/create_schema.out b/src/test/regress/expected/create_schema.out
index 93302a07ef..d2b97911cc 100644
--- a/src/test/regress/expected/create_schema.out
+++ b/src/test/regress/expected/create_schema.out
@@ -58,6 +58,57 @@ CREATE SCHEMA regress_schema_1 AUTHORIZATION CURRENT_ROLE
EXECUTE FUNCTION schema_trig.no_func();
ERROR: CREATE specifies a schema (schema_not_existing) different from the one being created (regress_schema_1)
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 table t(a ss)
+ create domain public.ss as text not null default 'hello' constraint nn check (value <> 'hello');
+ERROR: CREATE 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 table t(a ss)
+ create domain postgres.public.ss as text not null default 'hello' constraint nn check (value <> 'hello');
+ERROR: CREATE specifies a schema (public) different from the one being created (regress_schema_2)
+--fail. forward references, need reorder.
+CREATE SCHEMA regress_schema_2 AUTHORIZATION CURRENT_ROLE
+ create domain ss1 as ss
+ create domain ss as text;
+ERROR: type "ss" does not exist
+--ok, qualified schema name for domain should be same as the created schema.
+CREATE SCHEMA regress_schema_2 AUTHORIZATION CURRENT_ROLE
+ create table t(a regress_schema_2.ss)
+ create domain regress_schema_2.ss as text not null;
+\dD regress_schema_2.*
+ List of domains
+ Schema | Name | Type | Collation | Nullable | Default | Check
+------------------+------+------+-----------+----------+---------+-------
+ regress_schema_2 | ss | text | | not null | |
+(1 row)
+
+--ok, no qualified schema name for domain.
+CREATE SCHEMA regress_schema_3 AUTHORIZATION CURRENT_ROLE
+ create view test as select 'hello'::ss as test
+ create table t(a ss1)
+ create domain ss as text not null
+ create domain ss1 as ss;
+\dD regress_schema_3.*
+ List of domains
+ Schema | Name | Type | Collation | Nullable | Default | Check
+------------------+------+---------------------+-----------+----------+---------+-------
+ regress_schema_3 | ss | text | | not null | |
+ regress_schema_3 | ss1 | regress_schema_3.ss | | | |
+(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 table regress_schema_3.t
+drop cascades to view regress_schema_3.test
-- 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/create_schema.sql b/src/test/regress/sql/create_schema.sql
index 1b7064247a..421aaa424e 100644
--- a/src/test/regress/sql/create_schema.sql
+++ b/src/test/regress/sql/create_schema.sql
@@ -47,6 +47,39 @@ CREATE SCHEMA regress_schema_1 AUTHORIZATION CURRENT_ROLE
EXECUTE FUNCTION schema_trig.no_func();
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 table t(a ss)
+ create domain public.ss as text not null default 'hello' constraint nn check (value <> 'hello');
+--fail. cannot create domain to other schema
+CREATE SCHEMA regress_schema_2 AUTHORIZATION CURRENT_ROLE
+ create table t(a ss)
+ create domain postgres.public.ss as text not null default 'hello' constraint nn check (value <> 'hello');
+
+--fail. forward references, need reorder.
+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 table t(a regress_schema_2.ss)
+ create domain regress_schema_2.ss as text not null;
+\dD regress_schema_2.*
+
+--ok, no qualified schema name for domain.
+CREATE SCHEMA regress_schema_3 AUTHORIZATION CURRENT_ROLE
+ create view test as select 'hello'::ss as test
+ create table t(a ss1)
+ create domain ss as text not null
+ create domain ss1 as ss;
+\dD regress_schema_3.*
+
+DROP SCHEMA regress_schema_2 CASCADE;
+DROP SCHEMA regress_schema_3 CASCADE;
+
-- Cases where the schema creation succeeds.
-- The schema created matches the role name.
CREATE SCHEMA AUTHORIZATION regress_create_schema_role
--
2.34.1
^ permalink raw reply [nested|flat] 55+ messages in thread
* Re: CREATE SCHEMA ... CREATE DOMAIN support
2024-11-12 12:54 CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-23 05:19 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-27 03:42 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
@ 2024-11-27 03:47 ` Tom Lane <tgl@sss.pgh.pa.us>
1 sibling, 0 replies; 55+ messages in thread
From: Tom Lane @ 2024-11-27 03:47 UTC (permalink / raw)
To: jian he <jian.universality@gmail.com>; +Cc: Kirill Reshke <reshkekirill@gmail.com>; PostgreSQL Hackers <pgsql-hackers@lists.postgresql.org>
jian he <jian.universality@gmail.com> writes:
> one issue i still have is:
> CREATE SCHEMA regress_schema_2 AUTHORIZATION CURRENT_ROLE
> create domain ss1 as ss
> create domain ss as text;
> ERROR: type "ss" does not exist
> the error message seems not that OK,
> if we can point out the error position, that would be great.
That doesn't happen in the base case either:
regression=# create domain ss1 as ss;
ERROR: type "ss" does not exist
I doubt that fixing it should be part of this patch.
regards, tom lane
^ permalink raw reply [nested|flat] 55+ messages in thread
* Re: CREATE SCHEMA ... CREATE DOMAIN support
2024-11-12 12:54 CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-23 05:19 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-27 03:42 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
@ 2024-11-27 06:38 ` Kirill Reshke <reshkekirill@gmail.com>
2024-11-27 18:39 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
1 sibling, 1 reply; 55+ messages in thread
From: Kirill Reshke @ 2024-11-27 06:38 UTC (permalink / raw)
To: jian he <jian.universality@gmail.com>; +Cc: PostgreSQL Hackers <pgsql-hackers@lists.postgresql.org>
On Wed, 27 Nov 2024 at 08:42, jian he <jian.universality@gmail.com> wrote:
>
> On Sat, Nov 23, 2024 at 1:19 PM jian he <jian.universality@gmail.com> wrote:
> >
> > I didn't add a doc entry. I will do it later.
> hi
> attached patch with thorough tests and documentation.
>
Hi! Thanks for pushing this further.
> one issue i still have is:
> CREATE SCHEMA regress_schema_2 AUTHORIZATION CURRENT_ROLE
> create domain ss1 as ss
> create domain ss as text;
> ERROR: type "ss" does not exist
>
> the error message seems not that OK,
> if we can point out the error position, that would be great.
> like what we did with create schema create table:
>
> CREATE SCHEMA regress_schema_2 AUTHORIZATION CURRENT_ROLE
> create table t(a int, b x);
> ERROR: type "x" does not exist
> LINE 2: create table t(a int, b x);
> ^
To implement this, we need to include `ParseLoc location` to the
`CreateDomainStmt` struct, which is doubtful, because I don't see any
other type of create *something* that does this.
`make check` on v3 runs successfully. Test & doc seems fine to me.
PFA v4. The only change I made is for a commit message, and pg indent
run on this diff.
--
Best regards,
Kirill Reshke
Attachments:
[application/octet-stream] v4-0001-Extend-CREATE-SCHEMA-element-with-DOMAIN-support.patch (9.2K, ../../CALdSSPggNNvcad69dhUceZ_gPuEYnKNNd=WJ_WnP=YDmh=iwmw@mail.gmail.com/2-v4-0001-Extend-CREATE-SCHEMA-element-with-DOMAIN-support.patch)
download | inline diff:
From 390512c87be6384ed07e95b1b9d7e795611508dd Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Wed, 27 Nov 2024 11:27:40 +0800
Subject: [PATCH v4] Extend CREATE SCHEMA element with DOMAIN support.
SQL standart 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 view test as select 'hello'::ss as test
CREATE table t(a ss)
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 <reshkekirill@gmail.com>
Author: Jian He <jian.universality@gmail.com>
Reviewed-by: Alvaro Herrera <alvherre@alvh.no-ip.org>
Reviewed-by: Tom Lane <tgl@sss.pgh.pa.us>
discussion: https://postgr.es/m/CALdSSPh4jUSDsWu3K58hjO60wnTRR0DuO4CKRcwa8EVuOSfXxg%40mail.gmail.com
---
doc/src/sgml/ref/create_schema.sgml | 2 +-
src/backend/parser/gram.y | 1 +
src/backend/parser/parse_utilcmd.c | 27 +++++++++++
src/test/regress/expected/create_schema.out | 51 +++++++++++++++++++++
src/test/regress/sql/create_schema.sql | 33 +++++++++++++
5 files changed, 113 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/create_schema.sgml b/doc/src/sgml/ref/create_schema.sgml
index ed69298ccc6..06f6314a5bb 100644
--- a/doc/src/sgml/ref/create_schema.sgml
+++ b/doc/src/sgml/ref/create_schema.sgml
@@ -100,7 +100,7 @@ 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
+ 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
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 67eb96396af..ad8d9270acc 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -1584,6 +1584,7 @@ schema_stmt:
| CreateTrigStmt
| GrantStmt
| ViewStmt
+ | CreateDomainStmt
;
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 0f324ee4e31..45328eea168 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -105,6 +105,7 @@ typedef struct
List *indexes; /* CREATE INDEX items */
List *triggers; /* CREATE TRIGGER items */
List *grants; /* GRANT items */
+ List *domains; /* DOMAIN items */
} CreateSchemaStmtContext;
@@ -4039,6 +4040,7 @@ transformCreateSchemaStmtElements(List *schemaElts, const char *schemaName)
cxt.indexes = NIL;
cxt.triggers = NIL;
cxt.grants = NIL;
+ cxt.domains = NIL;
/*
* Run through each schema element in the schema element list. Separate
@@ -4107,6 +4109,30 @@ transformCreateSchemaStmtElements(List *schemaElts, const char *schemaName)
cxt.grants = lappend(cxt.grants, element);
break;
+ case T_CreateDomainStmt:
+ {
+ CreateDomainStmt *elp = (CreateDomainStmt *) element;
+ char *domain_schema = NULL;
+
+ /*
+ * DOMAIN's schema must the same as the to be created
+ * schema if length of domainname > 3 will fail at
+ * DeconstructQualifiedName,
+ */
+ if (list_length(elp->domainname) == 2)
+ {
+ domain_schema = strVal(list_nth(elp->domainname, 0));
+ setSchemaName(cxt.schemaname, &domain_schema);
+ }
+ else if (list_length(elp->domainname) == 3)
+ {
+ domain_schema = strVal(list_nth(elp->domainname, 1));
+ setSchemaName(cxt.schemaname, &domain_schema);
+ }
+ cxt.domains = lappend(cxt.domains, element);
+ }
+ break;
+
default:
elog(ERROR, "unrecognized node type: %d",
(int) nodeTag(element));
@@ -4114,6 +4140,7 @@ transformCreateSchemaStmtElements(List *schemaElts, const char *schemaName)
}
result = NIL;
+ result = list_concat(result, cxt.domains);
result = list_concat(result, cxt.sequences);
result = list_concat(result, cxt.tables);
result = list_concat(result, cxt.views);
diff --git a/src/test/regress/expected/create_schema.out b/src/test/regress/expected/create_schema.out
index 93302a07efc..d2b97911cc8 100644
--- a/src/test/regress/expected/create_schema.out
+++ b/src/test/regress/expected/create_schema.out
@@ -58,6 +58,57 @@ CREATE SCHEMA regress_schema_1 AUTHORIZATION CURRENT_ROLE
EXECUTE FUNCTION schema_trig.no_func();
ERROR: CREATE specifies a schema (schema_not_existing) different from the one being created (regress_schema_1)
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 table t(a ss)
+ create domain public.ss as text not null default 'hello' constraint nn check (value <> 'hello');
+ERROR: CREATE 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 table t(a ss)
+ create domain postgres.public.ss as text not null default 'hello' constraint nn check (value <> 'hello');
+ERROR: CREATE specifies a schema (public) different from the one being created (regress_schema_2)
+--fail. forward references, need reorder.
+CREATE SCHEMA regress_schema_2 AUTHORIZATION CURRENT_ROLE
+ create domain ss1 as ss
+ create domain ss as text;
+ERROR: type "ss" does not exist
+--ok, qualified schema name for domain should be same as the created schema.
+CREATE SCHEMA regress_schema_2 AUTHORIZATION CURRENT_ROLE
+ create table t(a regress_schema_2.ss)
+ create domain regress_schema_2.ss as text not null;
+\dD regress_schema_2.*
+ List of domains
+ Schema | Name | Type | Collation | Nullable | Default | Check
+------------------+------+------+-----------+----------+---------+-------
+ regress_schema_2 | ss | text | | not null | |
+(1 row)
+
+--ok, no qualified schema name for domain.
+CREATE SCHEMA regress_schema_3 AUTHORIZATION CURRENT_ROLE
+ create view test as select 'hello'::ss as test
+ create table t(a ss1)
+ create domain ss as text not null
+ create domain ss1 as ss;
+\dD regress_schema_3.*
+ List of domains
+ Schema | Name | Type | Collation | Nullable | Default | Check
+------------------+------+---------------------+-----------+----------+---------+-------
+ regress_schema_3 | ss | text | | not null | |
+ regress_schema_3 | ss1 | regress_schema_3.ss | | | |
+(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 table regress_schema_3.t
+drop cascades to view regress_schema_3.test
-- 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/create_schema.sql b/src/test/regress/sql/create_schema.sql
index 1b7064247a1..421aaa424e0 100644
--- a/src/test/regress/sql/create_schema.sql
+++ b/src/test/regress/sql/create_schema.sql
@@ -47,6 +47,39 @@ CREATE SCHEMA regress_schema_1 AUTHORIZATION CURRENT_ROLE
EXECUTE FUNCTION schema_trig.no_func();
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 table t(a ss)
+ create domain public.ss as text not null default 'hello' constraint nn check (value <> 'hello');
+--fail. cannot create domain to other schema
+CREATE SCHEMA regress_schema_2 AUTHORIZATION CURRENT_ROLE
+ create table t(a ss)
+ create domain postgres.public.ss as text not null default 'hello' constraint nn check (value <> 'hello');
+
+--fail. forward references, need reorder.
+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 table t(a regress_schema_2.ss)
+ create domain regress_schema_2.ss as text not null;
+\dD regress_schema_2.*
+
+--ok, no qualified schema name for domain.
+CREATE SCHEMA regress_schema_3 AUTHORIZATION CURRENT_ROLE
+ create view test as select 'hello'::ss as test
+ create table t(a ss1)
+ create domain ss as text not null
+ create domain ss1 as ss;
+\dD regress_schema_3.*
+
+DROP SCHEMA regress_schema_2 CASCADE;
+DROP SCHEMA regress_schema_3 CASCADE;
+
-- Cases where the schema creation succeeds.
-- The schema created matches the role name.
CREATE SCHEMA AUTHORIZATION regress_create_schema_role
--
2.34.1
^ permalink raw reply [nested|flat] 55+ messages in thread
* Re: CREATE SCHEMA ... CREATE DOMAIN support
2024-11-12 12:54 CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-23 05:19 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-27 03:42 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-27 06:38 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
@ 2024-11-27 18:39 ` Tom Lane <tgl@sss.pgh.pa.us>
2024-11-28 05:27 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
0 siblings, 1 reply; 55+ messages in thread
From: Tom Lane @ 2024-11-27 18:39 UTC (permalink / raw)
To: Kirill Reshke <reshkekirill@gmail.com>; +Cc: jian he <jian.universality@gmail.com>; PostgreSQL Hackers <pgsql-hackers@lists.postgresql.org>
Kirill Reshke <reshkekirill@gmail.com> writes:
> On Wed, 27 Nov 2024 at 08:42, jian he <jian.universality@gmail.com> wrote:
>> CREATE SCHEMA regress_schema_2 AUTHORIZATION CURRENT_ROLE
>> create domain ss1 as ss
>> create domain ss as text;
>> ERROR: type "ss" does not exist
>>
>> the error message seems not that OK,
>> if we can point out the error position, that would be great.
> To implement this, we need to include `ParseLoc location` to the
> `CreateDomainStmt` struct, which is doubtful, because I don't see any
> other type of create *something* that does this.
No, that error is thrown from typenameType(), which has a perfectly
good location in the TypeName. What it's lacking is a ParseState
containing the source query string.
Breakpoint 1, typenameType (pstate=pstate@entry=0x0, typeName=0x25d6b58,
typmod_p=typmod_p@entry=0x7ffe7dcd641c) at parse_type.c:268
268 tup = LookupTypeName(pstate, typeName, typmod_p, false);
(gdb) p pstate
$2 = (ParseState *) 0x0
(gdb) p typeName->location
$3 = 21
We've fixed a few utility statements so that they can receive
a passed-down ParseState, but not DefineDomain.
regards, tom lane
^ permalink raw reply [nested|flat] 55+ messages in thread
* Re: CREATE SCHEMA ... CREATE DOMAIN support
2024-11-12 12:54 CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-23 05:19 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-27 03:42 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-27 06:38 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-27 18:39 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
@ 2024-11-28 05:27 ` Kirill Reshke <reshkekirill@gmail.com>
2024-11-28 05:52 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
0 siblings, 1 reply; 55+ messages in thread
From: Kirill Reshke @ 2024-11-28 05:27 UTC (permalink / raw)
To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: jian he <jian.universality@gmail.com>; PostgreSQL Hackers <pgsql-hackers@lists.postgresql.org>
On Wed, 27 Nov 2024 at 23:39, Tom Lane <tgl@sss.pgh.pa.us> wrote:
>
> Kirill Reshke <reshkekirill@gmail.com> writes:
> > On Wed, 27 Nov 2024 at 08:42, jian he <jian.universality@gmail.com> wrote:
> >> CREATE SCHEMA regress_schema_2 AUTHORIZATION CURRENT_ROLE
> >> create domain ss1 as ss
> >> create domain ss as text;
> >> ERROR: type "ss" does not exist
> >>
> >> the error message seems not that OK,
> >> if we can point out the error position, that would be great.
>
> > To implement this, we need to include `ParseLoc location` to the
> > `CreateDomainStmt` struct, which is doubtful, because I don't see any
> > other type of create *something* that does this.
>
> No, that error is thrown from typenameType(), which has a perfectly
> good location in the TypeName. What it's lacking is a ParseState
> containing the source query string.
>
> Breakpoint 1, typenameType (pstate=pstate@entry=0x0, typeName=0x25d6b58,
> typmod_p=typmod_p@entry=0x7ffe7dcd641c) at parse_type.c:268
> 268 tup = LookupTypeName(pstate, typeName, typmod_p, false);
> (gdb) p pstate
> $2 = (ParseState *) 0x0
> (gdb) p typeName->location
> $3 = 21
>
> We've fixed a few utility statements so that they can receive
> a passed-down ParseState, but not DefineDomain.
>
> regards, tom lane
Indeed, my analysis is wrong.
Turns out passing parsestate to DefineDomain is itself enhancement.
Before this patch:
```
db1=# create domain ss1 as ss;
ERROR: type "ss" does not exist
```
after:
```
db1=# create domain ss1 as ss;
ERROR: type "ss" does not exist
LINE 1: create domain ss1 as ss;
^
```
PFA as an independent patch then. Or should we combine these two into one?
--
Best regards,
Kirill Reshke
Attachments:
[application/octet-stream] v1-0001-Pass-ParseState-as-first-param-to-DefineRelation.patch (2.7K, ../../CALdSSPhckRXW+KEvdsUmkQ-ErbrP_vPNjGwgXNdpXDb8xnLEbQ@mail.gmail.com/2-v1-0001-Pass-ParseState-as-first-param-to-DefineRelation.patch)
download | inline diff:
From e9a0908697ff89e73197b2a366b4c1c41a416975 Mon Sep 17 00:00:00 2001
From: reshke kirill <reshke@double.cloud>
Date: Thu, 28 Nov 2024 05:19:58 +0000
Subject: [PATCH v1] Pass ParseState as first param to DefineRelation
ParseState was lacking in typenameType call inside DefineDomain.
This patch fixes that. Now error-message for create domain
also show error location.
---
src/backend/commands/typecmds.c | 8 ++------
src/backend/tcop/utility.c | 2 +-
src/include/commands/typecmds.h | 2 +-
3 files changed, 4 insertions(+), 8 deletions(-)
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 859e2191f08..42407df89a5 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -694,7 +694,7 @@ RemoveTypeById(Oid typeOid)
* Registers a new domain.
*/
ObjectAddress
-DefineDomain(CreateDomainStmt *stmt)
+DefineDomain(ParseState *pstate, CreateDomainStmt *stmt)
{
char *domainName;
char *domainArrayName;
@@ -761,7 +761,7 @@ DefineDomain(CreateDomainStmt *stmt)
/*
* Look up the base type.
*/
- typeTup = typenameType(NULL, stmt->typeName, &basetypeMod);
+ typeTup = typenameType(pstate, stmt->typeName, &basetypeMod);
baseType = (Form_pg_type) GETSTRUCT(typeTup);
basetypeoid = baseType->oid;
@@ -885,12 +885,8 @@ DefineDomain(CreateDomainStmt *stmt)
if (constr->raw_expr)
{
- ParseState *pstate;
Node *defaultExpr;
- /* Create a dummy ParseState for transformExpr */
- pstate = make_parsestate(NULL);
-
/*
* Cook the constr->raw_expr into an expression. Note:
* name is strictly for error message
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index f28bf371059..33dea5a781c 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1712,7 +1712,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateDomainStmt:
- address = DefineDomain((CreateDomainStmt *) parsetree);
+ address = DefineDomain(pstate, (CreateDomainStmt *) parsetree);
break;
case T_CreateConversionStmt:
diff --git a/src/include/commands/typecmds.h b/src/include/commands/typecmds.h
index e1b02927c4b..cb30d1a2583 100644
--- a/src/include/commands/typecmds.h
+++ b/src/include/commands/typecmds.h
@@ -23,7 +23,7 @@
extern ObjectAddress DefineType(ParseState *pstate, List *names, List *parameters);
extern void RemoveTypeById(Oid typeOid);
-extern ObjectAddress DefineDomain(CreateDomainStmt *stmt);
+extern ObjectAddress DefineDomain(ParseState *pstate, CreateDomainStmt *stmt);
extern ObjectAddress DefineEnum(CreateEnumStmt *stmt);
extern ObjectAddress DefineRange(ParseState *pstate, CreateRangeStmt *stmt);
extern ObjectAddress AlterEnum(AlterEnumStmt *stmt);
--
2.34.1
^ permalink raw reply [nested|flat] 55+ messages in thread
* Re: CREATE SCHEMA ... CREATE DOMAIN support
2024-11-12 12:54 CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-23 05:19 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-27 03:42 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-27 06:38 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-27 18:39 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-11-28 05:27 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
@ 2024-11-28 05:52 ` Tom Lane <tgl@sss.pgh.pa.us>
2024-11-28 09:58 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
0 siblings, 1 reply; 55+ messages in thread
From: Tom Lane @ 2024-11-28 05:52 UTC (permalink / raw)
To: Kirill Reshke <reshkekirill@gmail.com>; +Cc: jian he <jian.universality@gmail.com>; PostgreSQL Hackers <pgsql-hackers@lists.postgresql.org>
Kirill Reshke <reshkekirill@gmail.com> writes:
> On Wed, 27 Nov 2024 at 23:39, Tom Lane <tgl@sss.pgh.pa.us> wrote:
>> We've fixed a few utility statements so that they can receive
>> a passed-down ParseState, but not DefineDomain.
> PFA as an independent patch then. Or should we combine these two into one?
No, I don't think this should be part of the patch discussed in this
thread.
It feels rather random to me to be fixing only DefineDomain;
I'm sure there's more in the same vein. I'd like to see a
patch with a scope along the lines of "fix everything reachable
within CREATE SCHEMA" or perhaps "fix all calls of typenameType".
(A quick grep shows that an outright majority of the callers of that
are passing null ParseState. I didn't look to see if any of them
have a good excuse beyond "we didn't do the plumbing work".)
regards, tom lane
^ permalink raw reply [nested|flat] 55+ messages in thread
* Re: CREATE SCHEMA ... CREATE DOMAIN support
2024-11-12 12:54 CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-23 05:19 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-27 03:42 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-27 06:38 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-27 18:39 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-11-28 05:27 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-28 05:52 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
@ 2024-11-28 09:58 ` Kirill Reshke <reshkekirill@gmail.com>
2024-11-29 13:47 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
0 siblings, 1 reply; 55+ messages in thread
From: Kirill Reshke @ 2024-11-28 09:58 UTC (permalink / raw)
To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: jian he <jian.universality@gmail.com>; PostgreSQL Hackers <pgsql-hackers@lists.postgresql.org>
On Thu, 28 Nov 2024 at 10:52, Tom Lane <tgl@sss.pgh.pa.us> wrote:
> No, I don't think this should be part of the patch discussed in this
> thread.
Ok, I created a separate thread for this.
How about this one? Do you think the suggested idea is good? Is it
worthwhile to do this, in your opinion?
--
Best regards,
Kirill Reshke
^ permalink raw reply [nested|flat] 55+ messages in thread
* Re: CREATE SCHEMA ... CREATE DOMAIN support
2024-11-12 12:54 CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-23 05:19 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-27 03:42 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-27 06:38 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-27 18:39 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-11-28 05:27 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-28 05:52 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-11-28 09:58 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
@ 2024-11-29 13:47 ` jian he <jian.universality@gmail.com>
2024-11-29 14:00 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-30 19:08 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
0 siblings, 2 replies; 55+ messages in thread
From: jian he @ 2024-11-29 13:47 UTC (permalink / raw)
To: Kirill Reshke <reshkekirill@gmail.com>; +Cc: Tom Lane <tgl@sss.pgh.pa.us>; PostgreSQL Hackers <pgsql-hackers@lists.postgresql.org>
new patch, add tab complete for it.
Attachments:
[text/x-patch] v5-0001-support-CREATE-SCHEMA-CREATE-DOMAIN.patch (10.7K, ../../CACJufxH5TvkQiMOJ9dpDrDbRtyCa-yu+QOmYGt4WGGYVrN+P8g@mail.gmail.com/2-v5-0001-support-CREATE-SCHEMA-CREATE-DOMAIN.patch)
download | inline diff:
From 91d05e547ca722d4537ff7420b8248a3fcce3b58 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Fri, 29 Nov 2024 21:44:54 +0800
Subject: [PATCH v5 1/1] support 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 view test as select 'hello'::ss as test
CREATE table t(a ss)
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 <reshkekirill@gmail.com>
Author: Jian He <jian.universality@gmail.com>
Reviewed-by: Alvaro Herrera <alvherre@alvh.no-ip.org>
Reviewed-by: Tom Lane <tgl@sss.pgh.pa.us>
---
doc/src/sgml/ref/create_schema.sgml | 2 +-
src/backend/parser/gram.y | 1 +
src/backend/parser/parse_utilcmd.c | 27 +++++++++++
src/bin/psql/tab-complete.in.c | 12 ++---
src/test/regress/expected/create_schema.out | 51 +++++++++++++++++++++
src/test/regress/sql/create_schema.sql | 33 +++++++++++++
6 files changed, 119 insertions(+), 7 deletions(-)
diff --git a/doc/src/sgml/ref/create_schema.sgml b/doc/src/sgml/ref/create_schema.sgml
index ed69298ccc..06f6314a5b 100644
--- a/doc/src/sgml/ref/create_schema.sgml
+++ b/doc/src/sgml/ref/create_schema.sgml
@@ -100,7 +100,7 @@ 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
+ 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
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 67eb96396a..ad8d9270ac 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -1584,6 +1584,7 @@ schema_stmt:
| CreateTrigStmt
| GrantStmt
| ViewStmt
+ | CreateDomainStmt
;
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 0f324ee4e3..45328eea16 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -105,6 +105,7 @@ typedef struct
List *indexes; /* CREATE INDEX items */
List *triggers; /* CREATE TRIGGER items */
List *grants; /* GRANT items */
+ List *domains; /* DOMAIN items */
} CreateSchemaStmtContext;
@@ -4039,6 +4040,7 @@ transformCreateSchemaStmtElements(List *schemaElts, const char *schemaName)
cxt.indexes = NIL;
cxt.triggers = NIL;
cxt.grants = NIL;
+ cxt.domains = NIL;
/*
* Run through each schema element in the schema element list. Separate
@@ -4107,6 +4109,30 @@ transformCreateSchemaStmtElements(List *schemaElts, const char *schemaName)
cxt.grants = lappend(cxt.grants, element);
break;
+ case T_CreateDomainStmt:
+ {
+ CreateDomainStmt *elp = (CreateDomainStmt *) element;
+ char *domain_schema = NULL;
+
+ /*
+ * DOMAIN's schema must the same as the to be created
+ * schema if length of domainname > 3 will fail at
+ * DeconstructQualifiedName,
+ */
+ if (list_length(elp->domainname) == 2)
+ {
+ domain_schema = strVal(list_nth(elp->domainname, 0));
+ setSchemaName(cxt.schemaname, &domain_schema);
+ }
+ else if (list_length(elp->domainname) == 3)
+ {
+ domain_schema = strVal(list_nth(elp->domainname, 1));
+ setSchemaName(cxt.schemaname, &domain_schema);
+ }
+ cxt.domains = lappend(cxt.domains, element);
+ }
+ break;
+
default:
elog(ERROR, "unrecognized node type: %d",
(int) nodeTag(element));
@@ -4114,6 +4140,7 @@ transformCreateSchemaStmtElements(List *schemaElts, const char *schemaName)
}
result = NIL;
+ result = list_concat(result, cxt.domains);
result = list_concat(result, cxt.sequences);
result = list_concat(result, cxt.tables);
result = list_concat(result, cxt.views);
diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c
index bbd08770c3..542429712a 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -2135,7 +2135,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
@@ -3293,15 +3293,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/regress/expected/create_schema.out b/src/test/regress/expected/create_schema.out
index 93302a07ef..d2b97911cc 100644
--- a/src/test/regress/expected/create_schema.out
+++ b/src/test/regress/expected/create_schema.out
@@ -58,6 +58,57 @@ CREATE SCHEMA regress_schema_1 AUTHORIZATION CURRENT_ROLE
EXECUTE FUNCTION schema_trig.no_func();
ERROR: CREATE specifies a schema (schema_not_existing) different from the one being created (regress_schema_1)
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 table t(a ss)
+ create domain public.ss as text not null default 'hello' constraint nn check (value <> 'hello');
+ERROR: CREATE 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 table t(a ss)
+ create domain postgres.public.ss as text not null default 'hello' constraint nn check (value <> 'hello');
+ERROR: CREATE specifies a schema (public) different from the one being created (regress_schema_2)
+--fail. forward references, need reorder.
+CREATE SCHEMA regress_schema_2 AUTHORIZATION CURRENT_ROLE
+ create domain ss1 as ss
+ create domain ss as text;
+ERROR: type "ss" does not exist
+--ok, qualified schema name for domain should be same as the created schema.
+CREATE SCHEMA regress_schema_2 AUTHORIZATION CURRENT_ROLE
+ create table t(a regress_schema_2.ss)
+ create domain regress_schema_2.ss as text not null;
+\dD regress_schema_2.*
+ List of domains
+ Schema | Name | Type | Collation | Nullable | Default | Check
+------------------+------+------+-----------+----------+---------+-------
+ regress_schema_2 | ss | text | | not null | |
+(1 row)
+
+--ok, no qualified schema name for domain.
+CREATE SCHEMA regress_schema_3 AUTHORIZATION CURRENT_ROLE
+ create view test as select 'hello'::ss as test
+ create table t(a ss1)
+ create domain ss as text not null
+ create domain ss1 as ss;
+\dD regress_schema_3.*
+ List of domains
+ Schema | Name | Type | Collation | Nullable | Default | Check
+------------------+------+---------------------+-----------+----------+---------+-------
+ regress_schema_3 | ss | text | | not null | |
+ regress_schema_3 | ss1 | regress_schema_3.ss | | | |
+(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 table regress_schema_3.t
+drop cascades to view regress_schema_3.test
-- 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/create_schema.sql b/src/test/regress/sql/create_schema.sql
index 1b7064247a..421aaa424e 100644
--- a/src/test/regress/sql/create_schema.sql
+++ b/src/test/regress/sql/create_schema.sql
@@ -47,6 +47,39 @@ CREATE SCHEMA regress_schema_1 AUTHORIZATION CURRENT_ROLE
EXECUTE FUNCTION schema_trig.no_func();
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 table t(a ss)
+ create domain public.ss as text not null default 'hello' constraint nn check (value <> 'hello');
+--fail. cannot create domain to other schema
+CREATE SCHEMA regress_schema_2 AUTHORIZATION CURRENT_ROLE
+ create table t(a ss)
+ create domain postgres.public.ss as text not null default 'hello' constraint nn check (value <> 'hello');
+
+--fail. forward references, need reorder.
+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 table t(a regress_schema_2.ss)
+ create domain regress_schema_2.ss as text not null;
+\dD regress_schema_2.*
+
+--ok, no qualified schema name for domain.
+CREATE SCHEMA regress_schema_3 AUTHORIZATION CURRENT_ROLE
+ create view test as select 'hello'::ss as test
+ create table t(a ss1)
+ create domain ss as text not null
+ create domain ss1 as ss;
+\dD regress_schema_3.*
+
+DROP SCHEMA regress_schema_2 CASCADE;
+DROP SCHEMA regress_schema_3 CASCADE;
+
-- Cases where the schema creation succeeds.
-- The schema created matches the role name.
CREATE SCHEMA AUTHORIZATION regress_create_schema_role
--
2.34.1
^ permalink raw reply [nested|flat] 55+ messages in thread
* Re: CREATE SCHEMA ... CREATE DOMAIN support
2024-11-12 12:54 CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-23 05:19 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-27 03:42 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-27 06:38 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-27 18:39 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-11-28 05:27 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-28 05:52 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-11-28 09:58 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-29 13:47 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
@ 2024-11-29 14:00 ` Kirill Reshke <reshkekirill@gmail.com>
1 sibling, 0 replies; 55+ messages in thread
From: Kirill Reshke @ 2024-11-29 14:00 UTC (permalink / raw)
To: jian he <jian.universality@gmail.com>; +Cc: Tom Lane <tgl@sss.pgh.pa.us>; PostgreSQL Hackers <pgsql-hackers@lists.postgresql.org>
On Fri, 29 Nov 2024 at 18:47, jian he <jian.universality@gmail.com> wrote:
>
> new patch, add tab complete for it.
Thank you. You may also be interested in reviewing [0].
[0] https://www.postgresql.org/message-id/CALdSSPhqfvKbDwqJaY%3DyEePi_aq61GmMpW88i6ZH7CMG_2Z4Cg%40mail.g...
--
Best regards,
Kirill Reshke
^ permalink raw reply [nested|flat] 55+ messages in thread
* Re: CREATE SCHEMA ... CREATE DOMAIN support
2024-11-12 12:54 CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-23 05:19 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-27 03:42 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-27 06:38 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-27 18:39 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-11-28 05:27 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-28 05:52 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-11-28 09:58 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-29 13:47 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
@ 2024-11-30 19:08 ` Tom Lane <tgl@sss.pgh.pa.us>
2024-11-30 23:33 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-12-03 19:35 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Peter Eisentraut <peter@eisentraut.org>
1 sibling, 2 replies; 55+ messages in thread
From: Tom Lane @ 2024-11-30 19:08 UTC (permalink / raw)
To: jian he <jian.universality@gmail.com>; +Cc: Peter Eisentraut <peter@eisentraut.org>; Kirill Reshke <reshkekirill@gmail.com>; PostgreSQL Hackers <pgsql-hackers@lists.postgresql.org>
[ 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] 55+ messages in thread
* Re: CREATE SCHEMA ... CREATE DOMAIN support
2024-11-12 12:54 CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-23 05:19 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-27 03:42 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-27 06:38 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-27 18:39 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-11-28 05:27 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-28 05:52 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-11-28 09:58 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-29 13:47 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-30 19:08 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
@ 2024-11-30 23:33 ` Tom Lane <tgl@sss.pgh.pa.us>
2024-12-01 05:39 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
1 sibling, 1 reply; 55+ messages in thread
From: Tom Lane @ 2024-11-30 23:33 UTC (permalink / raw)
To: Peter Eisentraut <peter@eisentraut.org>; +Cc: jian he <jian.universality@gmail.com>; Kirill Reshke <reshkekirill@gmail.com>; PostgreSQL Hackers <pgsql-hackers@lists.postgresql.org>
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, ../../1220935.1733009604@sss.pgh.pa.us/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 <tgl@sss.pgh.pa.us>
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/1075425.1732993688@sss.pgh.pa.us
---
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] 55+ messages in thread
* Re: CREATE SCHEMA ... CREATE DOMAIN support
2024-11-12 12:54 CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-23 05:19 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-27 03:42 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-27 06:38 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-27 18:39 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-11-28 05:27 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-28 05:52 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-11-28 09:58 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-29 13:47 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-30 19:08 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-11-30 23:33 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
@ 2024-12-01 05:39 ` Kirill Reshke <reshkekirill@gmail.com>
2024-12-01 05:53 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
0 siblings, 1 reply; 55+ messages in thread
From: Kirill Reshke @ 2024-12-01 05:39 UTC (permalink / raw)
To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Peter Eisentraut <peter@eisentraut.org>; jian he <jian.universality@gmail.com>; PostgreSQL Hackers <pgsql-hackers@lists.postgresql.org>
On Sun, 1 Dec 2024 at 04:33, Tom Lane <tgl@sss.pgh.pa.us> 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, ../../CALdSSPi1tgLgXjSJ7jgQ7L65iRkpuDFL2cJH2QYSz-Hhx3DBew@mail.gmail.com/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 <tgl@sss.pgh.pa.us>
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/1075425.1732993688@sss.pgh.pa.us
---
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] 55+ messages in thread
* Re: CREATE SCHEMA ... CREATE DOMAIN support
2024-11-12 12:54 CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-23 05:19 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-27 03:42 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-27 06:38 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-27 18:39 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-11-28 05:27 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-28 05:52 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-11-28 09:58 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-29 13:47 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-30 19:08 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-11-30 23:33 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-12-01 05:39 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
@ 2024-12-01 05:53 ` Tom Lane <tgl@sss.pgh.pa.us>
2024-12-01 09:59 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
0 siblings, 1 reply; 55+ messages in thread
From: Tom Lane @ 2024-12-01 05:53 UTC (permalink / raw)
To: Kirill Reshke <reshkekirill@gmail.com>; +Cc: Peter Eisentraut <peter@eisentraut.org>; jian he <jian.universality@gmail.com>; PostgreSQL Hackers <pgsql-hackers@lists.postgresql.org>
Kirill Reshke <reshkekirill@gmail.com> 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] 55+ messages in thread
* Re: CREATE SCHEMA ... CREATE DOMAIN support
2024-11-12 12:54 CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-23 05:19 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-27 03:42 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-27 06:38 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-27 18:39 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-11-28 05:27 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-28 05:52 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-11-28 09:58 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-29 13:47 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-30 19:08 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-11-30 23:33 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-12-01 05:39 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-12-01 05:53 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
@ 2024-12-01 09:59 ` jian he <jian.universality@gmail.com>
2024-12-01 16:57 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
0 siblings, 1 reply; 55+ messages in thread
From: jian he @ 2024-12-01 09:59 UTC (permalink / raw)
To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Kirill Reshke <reshkekirill@gmail.com>; Peter Eisentraut <peter@eisentraut.org>; PostgreSQL Hackers <pgsql-hackers@lists.postgresql.org>
On Sun, Dec 1, 2024 at 1:53 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
>
> Kirill Reshke <reshkekirill@gmail.com> 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] 55+ messages in thread
* Re: CREATE SCHEMA ... CREATE DOMAIN support
2024-11-12 12:54 CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-23 05:19 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-27 03:42 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-27 06:38 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-27 18:39 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-11-28 05:27 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-28 05:52 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-11-28 09:58 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-29 13:47 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-30 19:08 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-11-30 23:33 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-12-01 05:39 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-12-01 05:53 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-12-01 09:59 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
@ 2024-12-01 16:57 ` Tom Lane <tgl@sss.pgh.pa.us>
2024-12-01 22:30 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
0 siblings, 1 reply; 55+ messages in thread
From: Tom Lane @ 2024-12-01 16:57 UTC (permalink / raw)
To: jian he <jian.universality@gmail.com>; +Cc: Kirill Reshke <reshkekirill@gmail.com>; Peter Eisentraut <peter@eisentraut.org>; PostgreSQL Hackers <pgsql-hackers@lists.postgresql.org>
jian he <jian.universality@gmail.com> writes:
> On Sun, Dec 1, 2024 at 1:53 PM Tom Lane <tgl@sss.pgh.pa.us> 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] 55+ messages in thread
* Re: CREATE SCHEMA ... CREATE DOMAIN support
2024-11-12 12:54 CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-23 05:19 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-27 03:42 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-27 06:38 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-27 18:39 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-11-28 05:27 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-28 05:52 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-11-28 09:58 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-29 13:47 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-30 19:08 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-11-30 23:33 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-12-01 05:39 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-12-01 05:53 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-12-01 09:59 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-12-01 16:57 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
@ 2024-12-01 22:30 ` Tom Lane <tgl@sss.pgh.pa.us>
2024-12-02 01:34 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Michael Paquier <michael@paquier.xyz>
0 siblings, 1 reply; 55+ messages in thread
From: Tom Lane @ 2024-12-01 22:30 UTC (permalink / raw)
To: jian he <jian.universality@gmail.com>; +Cc: Kirill Reshke <reshkekirill@gmail.com>; Peter Eisentraut <peter@eisentraut.org>; PostgreSQL Hackers <pgsql-hackers@lists.postgresql.org>
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] 55+ messages in thread
* Re: CREATE SCHEMA ... CREATE DOMAIN support
2024-11-12 12:54 CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-23 05:19 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-27 03:42 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-27 06:38 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-27 18:39 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-11-28 05:27 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-28 05:52 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-11-28 09:58 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-29 13:47 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-30 19:08 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-11-30 23:33 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-12-01 05:39 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-12-01 05:53 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-12-01 09:59 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-12-01 16:57 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-12-01 22:30 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
@ 2024-12-02 01:34 ` Michael Paquier <michael@paquier.xyz>
2024-12-02 02:15 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
0 siblings, 1 reply; 55+ messages in thread
From: Michael Paquier @ 2024-12-02 01:34 UTC (permalink / raw)
To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: jian he <jian.universality@gmail.com>; Kirill Reshke <reshkekirill@gmail.com>; Peter Eisentraut <peter@eisentraut.org>; PostgreSQL Hackers <pgsql-hackers@lists.postgresql.org>
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 (832B, ../../Z00OwQVHY4DfjVSA@paquier.xyz/2-signature.asc)
download
^ permalink raw reply [nested|flat] 55+ messages in thread
* Re: CREATE SCHEMA ... CREATE DOMAIN support
2024-11-12 12:54 CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-23 05:19 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-27 03:42 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-27 06:38 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-27 18:39 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-11-28 05:27 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-28 05:52 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-11-28 09:58 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-29 13:47 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-30 19:08 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-11-30 23:33 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-12-01 05:39 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-12-01 05:53 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-12-01 09:59 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-12-01 16:57 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-12-01 22:30 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-12-02 01:34 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Michael Paquier <michael@paquier.xyz>
@ 2024-12-02 02:15 ` Tom Lane <tgl@sss.pgh.pa.us>
2024-12-02 16:08 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Vik Fearing <vik@postgresfriends.org>
0 siblings, 1 reply; 55+ messages in thread
From: Tom Lane @ 2024-12-02 02:15 UTC (permalink / raw)
To: Michael Paquier <michael@paquier.xyz>; +Cc: jian he <jian.universality@gmail.com>; Kirill Reshke <reshkekirill@gmail.com>; Peter Eisentraut <peter@eisentraut.org>; PostgreSQL Hackers <pgsql-hackers@lists.postgresql.org>
Michael Paquier <michael@paquier.xyz> 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] 55+ messages in thread
* Re: CREATE SCHEMA ... CREATE DOMAIN support
2024-11-12 12:54 CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-23 05:19 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-27 03:42 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-27 06:38 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-27 18:39 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-11-28 05:27 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-28 05:52 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-11-28 09:58 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-29 13:47 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-30 19:08 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-11-30 23:33 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-12-01 05:39 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-12-01 05:53 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-12-01 09:59 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-12-01 16:57 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-12-01 22:30 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-12-02 01:34 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Michael Paquier <michael@paquier.xyz>
2024-12-02 02:15 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
@ 2024-12-02 16:08 ` Vik Fearing <vik@postgresfriends.org>
2024-12-02 16:56 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
0 siblings, 1 reply; 55+ messages in thread
From: Vik Fearing @ 2024-12-02 16:08 UTC (permalink / raw)
To: Tom Lane <tgl@sss.pgh.pa.us>; Michael Paquier <michael@paquier.xyz>; +Cc: jian he <jian.universality@gmail.com>; Kirill Reshke <reshkekirill@gmail.com>; Peter Eisentraut <peter@eisentraut.org>; PostgreSQL Hackers <pgsql-hackers@lists.postgresql.org>
On 02/12/2024 03:15, Tom Lane wrote:
> Michael Paquier <michael@paquier.xyz> 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] 55+ messages in thread
* Re: CREATE SCHEMA ... CREATE DOMAIN support
2024-11-12 12:54 CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-23 05:19 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-27 03:42 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-27 06:38 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-27 18:39 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-11-28 05:27 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-28 05:52 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-11-28 09:58 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-29 13:47 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-30 19:08 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-11-30 23:33 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-12-01 05:39 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-12-01 05:53 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-12-01 09:59 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-12-01 16:57 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-12-01 22:30 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-12-02 01:34 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Michael Paquier <michael@paquier.xyz>
2024-12-02 02:15 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-12-02 16:08 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Vik Fearing <vik@postgresfriends.org>
@ 2024-12-02 16:56 ` Tom Lane <tgl@sss.pgh.pa.us>
2024-12-02 17:26 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Vik Fearing <vik@postgresfriends.org>
0 siblings, 1 reply; 55+ messages in thread
From: Tom Lane @ 2024-12-02 16:56 UTC (permalink / raw)
To: Vik Fearing <vik@postgresfriends.org>; +Cc: Michael Paquier <michael@paquier.xyz>; jian he <jian.universality@gmail.com>; Kirill Reshke <reshkekirill@gmail.com>; Peter Eisentraut <peter@eisentraut.org>; PostgreSQL Hackers <pgsql-hackers@lists.postgresql.org>
Vik Fearing <vik@postgresfriends.org> 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] 55+ messages in thread
* Re: CREATE SCHEMA ... CREATE DOMAIN support
2024-11-12 12:54 CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-23 05:19 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-27 03:42 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-27 06:38 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-27 18:39 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-11-28 05:27 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-28 05:52 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-11-28 09:58 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-29 13:47 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-30 19:08 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-11-30 23:33 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-12-01 05:39 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-12-01 05:53 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-12-01 09:59 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-12-01 16:57 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-12-01 22:30 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-12-02 01:34 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Michael Paquier <michael@paquier.xyz>
2024-12-02 02:15 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-12-02 16:08 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Vik Fearing <vik@postgresfriends.org>
2024-12-02 16:56 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
@ 2024-12-02 17:26 ` Vik Fearing <vik@postgresfriends.org>
0 siblings, 0 replies; 55+ messages in thread
From: Vik Fearing @ 2024-12-02 17:26 UTC (permalink / raw)
To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Michael Paquier <michael@paquier.xyz>; jian he <jian.universality@gmail.com>; Kirill Reshke <reshkekirill@gmail.com>; Peter Eisentraut <peter@eisentraut.org>; PostgreSQL Hackers <pgsql-hackers@lists.postgresql.org>
On 02/12/2024 17:56, Tom Lane wrote:
> Vik Fearing <vik@postgresfriends.org> 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] 55+ messages in thread
* Re: CREATE SCHEMA ... CREATE DOMAIN support
2024-11-12 12:54 CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-23 05:19 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-27 03:42 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-27 06:38 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-27 18:39 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-11-28 05:27 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-28 05:52 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-11-28 09:58 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-29 13:47 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-30 19:08 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
@ 2024-12-03 19:35 ` Peter Eisentraut <peter@eisentraut.org>
2024-12-03 20:07 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
1 sibling, 1 reply; 55+ messages in thread
From: Peter Eisentraut @ 2024-12-03 19:35 UTC (permalink / raw)
To: Tom Lane <tgl@sss.pgh.pa.us>; jian he <jian.universality@gmail.com>; +Cc: Kirill Reshke <reshkekirill@gmail.com>; PostgreSQL Hackers <pgsql-hackers@lists.postgresql.org>
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] 55+ messages in thread
* Re: CREATE SCHEMA ... CREATE DOMAIN support
2024-11-12 12:54 CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-23 05:19 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-27 03:42 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-27 06:38 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-27 18:39 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-11-28 05:27 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-28 05:52 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-11-28 09:58 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-29 13:47 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-30 19:08 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-12-03 19:35 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Peter Eisentraut <peter@eisentraut.org>
@ 2024-12-03 20:07 ` Tom Lane <tgl@sss.pgh.pa.us>
2024-12-11 06:22 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
0 siblings, 1 reply; 55+ messages in thread
From: Tom Lane @ 2024-12-03 20:07 UTC (permalink / raw)
To: Peter Eisentraut <peter@eisentraut.org>; +Cc: jian he <jian.universality@gmail.com>; Kirill Reshke <reshkekirill@gmail.com>; PostgreSQL Hackers <pgsql-hackers@lists.postgresql.org>
Peter Eisentraut <peter@eisentraut.org> 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] 55+ messages in thread
* Re: CREATE SCHEMA ... CREATE DOMAIN support
2024-11-12 12:54 CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-23 05:19 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-27 03:42 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-27 06:38 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-27 18:39 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-11-28 05:27 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-28 05:52 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-11-28 09:58 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-29 13:47 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-30 19:08 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-12-03 19:35 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Peter Eisentraut <peter@eisentraut.org>
2024-12-03 20:07 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
@ 2024-12-11 06:22 ` Kirill Reshke <reshkekirill@gmail.com>
2024-12-11 17:08 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
0 siblings, 1 reply; 55+ messages in thread
From: Kirill Reshke @ 2024-12-11 06:22 UTC (permalink / raw)
To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Peter Eisentraut <peter@eisentraut.org>; jian he <jian.universality@gmail.com>; PostgreSQL Hackers <pgsql-hackers@lists.postgresql.org>
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 <tgl@sss.pgh.pa.us> 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] 55+ messages in thread
* Re: CREATE SCHEMA ... CREATE DOMAIN support
2024-11-12 12:54 CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-23 05:19 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-27 03:42 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-27 06:38 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-27 18:39 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-11-28 05:27 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-28 05:52 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-11-28 09:58 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-29 13:47 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-30 19:08 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-12-03 19:35 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Peter Eisentraut <peter@eisentraut.org>
2024-12-03 20:07 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-12-11 06:22 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
@ 2024-12-11 17:08 ` Tom Lane <tgl@sss.pgh.pa.us>
2025-08-19 03:36 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
0 siblings, 1 reply; 55+ messages in thread
From: Tom Lane @ 2024-12-11 17:08 UTC (permalink / raw)
To: Kirill Reshke <reshkekirill@gmail.com>; +Cc: Peter Eisentraut <peter@eisentraut.org>; jian he <jian.universality@gmail.com>; PostgreSQL Hackers <pgsql-hackers@lists.postgresql.org>
Kirill Reshke <reshkekirill@gmail.com> writes:
> On Wed, 4 Dec 2024 at 01:07, Tom Lane <tgl@sss.pgh.pa.us> 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] 55+ messages in thread
* Re: CREATE SCHEMA ... CREATE DOMAIN support
2024-11-12 12:54 CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-23 05:19 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-27 03:42 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-27 06:38 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-27 18:39 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-11-28 05:27 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-28 05:52 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-11-28 09:58 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-29 13:47 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-30 19:08 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-12-03 19:35 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Peter Eisentraut <peter@eisentraut.org>
2024-12-03 20:07 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-12-11 06:22 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-12-11 17:08 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
@ 2025-08-19 03:36 ` jian he <jian.universality@gmail.com>
2025-08-22 08:59 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
0 siblings, 1 reply; 55+ messages in thread
From: jian he @ 2025-08-19 03:36 UTC (permalink / raw)
To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Kirill Reshke <reshkekirill@gmail.com>; Peter Eisentraut <peter@eisentraut.org>; PostgreSQL Hackers <pgsql-hackers@lists.postgresql.org>
On Thu, Dec 12, 2024 at 1:08 AM Tom Lane <tgl@sss.pgh.pa.us> 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, ../../CACJufxF28Vk27JhJ9u8tq10BoHLg=T9=wJ8zqTy_ajM4=Czunw@mail.gmail.com/2-v6-0002-CREATE-SCHEMA-CREATE-DOMAIN.patch)
download | inline diff:
From 162ca0e712a3bbe16193275104e7c52739633503 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
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 <reshkekirill@gmail.com>
Author: Jian He <jian.universality@gmail.com>
Reviewed-by: Alvaro Herrera <alvherre@alvh.no-ip.org>
Reviewed-by: Tom Lane <tgl@sss.pgh.pa.us>
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, ../../CACJufxF28Vk27JhJ9u8tq10BoHLg=T9=wJ8zqTy_ajM4=Czunw@mail.gmail.com/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 <tgl@sss.pgh.pa.us>
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/1075425.1732993688@sss.pgh.pa.us
---
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, ../../CACJufxF28Vk27JhJ9u8tq10BoHLg=T9=wJ8zqTy_ajM4=Czunw@mail.gmail.com/4-v6-0003-CREATE-SCHEMA-CREATE-COLLATION.patch)
download | inline diff:
From 9ff37e0923317cbce1722ea4c0260e72ce0db9c2 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
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] 55+ messages in thread
* Re: CREATE SCHEMA ... CREATE DOMAIN support
2024-11-12 12:54 CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-23 05:19 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-27 03:42 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-27 06:38 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-27 18:39 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-11-28 05:27 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-28 05:52 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-11-28 09:58 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-29 13:47 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-30 19:08 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-12-03 19:35 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Peter Eisentraut <peter@eisentraut.org>
2024-12-03 20:07 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-12-11 06:22 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-12-11 17:08 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2025-08-19 03:36 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
@ 2025-08-22 08:59 ` Kirill Reshke <reshkekirill@gmail.com>
2025-08-26 04:53 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2025-09-02 21:24 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
0 siblings, 2 replies; 55+ messages in thread
From: Kirill Reshke @ 2025-08-22 08:59 UTC (permalink / raw)
To: jian he <jian.universality@gmail.com>; +Cc: Tom Lane <tgl@sss.pgh.pa.us>; Peter Eisentraut <peter@eisentraut.org>; PostgreSQL Hackers <pgsql-hackers@lists.postgresql.org>
On Tue, 19 Aug 2025 at 08:37, jian he <jian.universality@gmail.com> wrote:
>
> On Thu, Dec 12, 2024 at 1:08 AM Tom Lane <tgl@sss.pgh.pa.us> 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] 55+ messages in thread
* Re: CREATE SCHEMA ... CREATE DOMAIN support
2024-11-12 12:54 CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-23 05:19 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-27 03:42 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-27 06:38 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-27 18:39 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-11-28 05:27 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-28 05:52 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-11-28 09:58 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-29 13:47 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-30 19:08 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-12-03 19:35 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Peter Eisentraut <peter@eisentraut.org>
2024-12-03 20:07 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-12-11 06:22 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-12-11 17:08 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2025-08-19 03:36 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2025-08-22 08:59 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
@ 2025-08-26 04:53 ` jian he <jian.universality@gmail.com>
1 sibling, 0 replies; 55+ messages in thread
From: jian he @ 2025-08-26 04:53 UTC (permalink / raw)
To: Kirill Reshke <reshkekirill@gmail.com>; +Cc: Tom Lane <tgl@sss.pgh.pa.us>; Peter Eisentraut <peter@eisentraut.org>; PostgreSQL Hackers <pgsql-hackers@lists.postgresql.org>
On Fri, Aug 22, 2025 at 4:59 PM Kirill Reshke <reshkekirill@gmail.com> 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, ../../CACJufxGOtXuJD3VLOMUQr08Zt3=8HjH0KYk2NivYPuMbHG2JQg@mail.gmail.com/2-v7-0002-CREATE-SCHEMA-CREATE-DOMAIN.patch)
download | inline diff:
From 234796bc14a3ae8413833718a86aeffbc110ae48 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
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 <reshkekirill@gmail.com>
Author: Jian He <jian.universality@gmail.com>
Reviewed-by: Alvaro Herrera <alvherre@alvh.no-ip.org>
Reviewed-by: Tom Lane <tgl@sss.pgh.pa.us>
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, ../../CACJufxGOtXuJD3VLOMUQr08Zt3=8HjH0KYk2NivYPuMbHG2JQg@mail.gmail.com/3-v7-0003-CREATE-SCHEMA-CREATE-COLLATION.patch)
download | inline diff:
From cf95a41428b022e6361f9a4a0e00a1a9474a34b6 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
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, ../../CACJufxGOtXuJD3VLOMUQr08Zt3=8HjH0KYk2NivYPuMbHG2JQg@mail.gmail.com/4-v7-0004-CREATE-SCHEMA-CREATE-TYPE.patch)
download | inline diff:
From 8c1aae1069a7b8507c0f26a70561e44675599b85 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
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, ../../CACJufxGOtXuJD3VLOMUQr08Zt3=8HjH0KYk2NivYPuMbHG2JQg@mail.gmail.com/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 <tgl@sss.pgh.pa.us>
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/1075425.1732993688@sss.pgh.pa.us
---
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] 55+ messages in thread
* Re: CREATE SCHEMA ... CREATE DOMAIN support
2024-11-12 12:54 CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-23 05:19 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-27 03:42 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-27 06:38 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-27 18:39 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-11-28 05:27 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-28 05:52 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-11-28 09:58 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-29 13:47 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-30 19:08 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-12-03 19:35 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Peter Eisentraut <peter@eisentraut.org>
2024-12-03 20:07 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-12-11 06:22 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-12-11 17:08 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2025-08-19 03:36 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2025-08-22 08:59 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
@ 2025-09-02 21:24 ` Tom Lane <tgl@sss.pgh.pa.us>
2025-12-17 13:29 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
1 sibling, 1 reply; 55+ messages in thread
From: Tom Lane @ 2025-09-02 21:24 UTC (permalink / raw)
To: jian he <jian.universality@gmail.com>; +Cc: Kirill Reshke <reshkekirill@gmail.com>; Peter Eisentraut <peter@eisentraut.org>; PostgreSQL Hackers <pgsql-hackers@lists.postgresql.org>
jian he <jian.universality@gmail.com> 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] 55+ messages in thread
* Re: CREATE SCHEMA ... CREATE DOMAIN support
2024-11-12 12:54 CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-23 05:19 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-27 03:42 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-27 06:38 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-27 18:39 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-11-28 05:27 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-28 05:52 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-11-28 09:58 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-29 13:47 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-30 19:08 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-12-03 19:35 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Peter Eisentraut <peter@eisentraut.org>
2024-12-03 20:07 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-12-11 06:22 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-12-11 17:08 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2025-08-19 03:36 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2025-08-22 08:59 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2025-09-02 21:24 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
@ 2025-12-17 13:29 ` jian he <jian.universality@gmail.com>
2025-12-31 01:40 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2026-04-03 18:35 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
0 siblings, 2 replies; 55+ messages in thread
From: jian he @ 2025-12-17 13:29 UTC (permalink / raw)
To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Kirill Reshke <reshkekirill@gmail.com>; Peter Eisentraut <peter@eisentraut.org>; PostgreSQL Hackers <pgsql-hackers@lists.postgresql.org>
On Wed, Sep 3, 2025 at 5:24 AM Tom Lane <tgl@sss.pgh.pa.us> wrote:
>
> jian he <jian.universality@gmail.com> 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, ../../CACJufxG7okkEs+kjCxR0b=6HnzBmMjkUszndT7puTQq6ADXwtQ@mail.gmail.com/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, ../../CACJufxG7okkEs+kjCxR0b=6HnzBmMjkUszndT7puTQq6ADXwtQ@mail.gmail.com/3-v8-0001-Don-t-try-to-re-order-the-subcommands-of-CREATE-SCHEMA.nocfbot)
download
^ permalink raw reply [nested|flat] 55+ messages in thread
* Re: CREATE SCHEMA ... CREATE DOMAIN support
2024-11-12 12:54 CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-23 05:19 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-27 03:42 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-27 06:38 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-27 18:39 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-11-28 05:27 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-28 05:52 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-11-28 09:58 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-29 13:47 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-30 19:08 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-12-03 19:35 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Peter Eisentraut <peter@eisentraut.org>
2024-12-03 20:07 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-12-11 06:22 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-12-11 17:08 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2025-08-19 03:36 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2025-08-22 08:59 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2025-09-02 21:24 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2025-12-17 13:29 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
@ 2025-12-31 01:40 ` jian he <jian.universality@gmail.com>
2026-01-19 15:06 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
1 sibling, 1 reply; 55+ messages in thread
From: jian he @ 2025-12-31 01:40 UTC (permalink / raw)
To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Kirill Reshke <reshkekirill@gmail.com>; Peter Eisentraut <peter@eisentraut.org>; PostgreSQL Hackers <pgsql-hackers@lists.postgresql.org>
> On Wed, Sep 3, 2025 at 5:24 AM Tom Lane <tgl@sss.pgh.pa.us> 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, ../../CACJufxGROyDj-crKiwZzEDdq4q3N0ngywGNiYz1=8LXzVwRXPQ@mail.gmail.com/2-v8-0003-CREATE-SCHEMA-CREATE-COLLATION.patch)
download | inline diff:
From f585d3966cedf6046a863a7a9018a3ffd8399559 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
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, ../../CACJufxGROyDj-crKiwZzEDdq4q3N0ngywGNiYz1=8LXzVwRXPQ@mail.gmail.com/3-v8-0004-CREATE-SCHEMA-CREATE-TYPE.patch)
download | inline diff:
From 1d4d10a467adf7ffed45f73dd56166c6fedd5e6d Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
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, ../../CACJufxGROyDj-crKiwZzEDdq4q3N0ngywGNiYz1=8LXzVwRXPQ@mail.gmail.com/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 <jian.universality@gmail.com>
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/1075425.1732993688@sss.pgh.pa.us
---
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, ../../CACJufxGROyDj-crKiwZzEDdq4q3N0ngywGNiYz1=8LXzVwRXPQ@mail.gmail.com/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 <jian.universality@gmail.com>
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/1075425.1732993688@sss.pgh.pa.us
---
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, ../../CACJufxGROyDj-crKiwZzEDdq4q3N0ngywGNiYz1=8LXzVwRXPQ@mail.gmail.com/6-v8-0002-CREATE-SCHEMA-CREATE-DOMAIN.patch)
download | inline diff:
From fc8c3f52c87d0fe057487a185d3735efd42eb456 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
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 <reshkekirill@gmail.com>
Author: Jian He <jian.universality@gmail.com>
Reviewed-by: Alvaro Herrera <alvherre@alvh.no-ip.org>
Reviewed-by: Tom Lane <tgl@sss.pgh.pa.us>
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] 55+ messages in thread
* Re: CREATE SCHEMA ... CREATE DOMAIN support
2024-11-12 12:54 CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-23 05:19 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-27 03:42 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-27 06:38 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-27 18:39 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-11-28 05:27 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-28 05:52 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-11-28 09:58 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-29 13:47 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-30 19:08 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-12-03 19:35 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Peter Eisentraut <peter@eisentraut.org>
2024-12-03 20:07 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-12-11 06:22 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-12-11 17:08 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2025-08-19 03:36 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2025-08-22 08:59 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2025-09-02 21:24 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2025-12-17 13:29 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2025-12-31 01:40 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
@ 2026-01-19 15:06 ` jian he <jian.universality@gmail.com>
2026-03-10 14:11 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
0 siblings, 1 reply; 55+ messages in thread
From: jian he @ 2026-01-19 15:06 UTC (permalink / raw)
To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Kirill Reshke <reshkekirill@gmail.com>; Peter Eisentraut <peter@eisentraut.org>; PostgreSQL Hackers <pgsql-hackers@lists.postgresql.org>
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, ../../CACJufxG1NfL_7P31v1GHe5GamYE1J5OWxug36EG3rFt6swzG3w@mail.gmail.com/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 <jian.universality@gmail.com>
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/1075425.1732993688@sss.pgh.pa.us
---
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, ../../CACJufxG1NfL_7P31v1GHe5GamYE1J5OWxug36EG3rFt6swzG3w@mail.gmail.com/3-v9-0004-CREATE-SCHEMA-CREATE-TYPE.patch)
download | inline diff:
From a526fb90046ee1c6087961e45607e5406f7fc7b6 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
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, ../../CACJufxG1NfL_7P31v1GHe5GamYE1J5OWxug36EG3rFt6swzG3w@mail.gmail.com/4-v9-0002-CREATE-SCHEMA-CREATE-DOMAIN.patch)
download | inline diff:
From fc8c3f52c87d0fe057487a185d3735efd42eb456 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
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 <reshkekirill@gmail.com>
Author: Jian He <jian.universality@gmail.com>
Reviewed-by: Alvaro Herrera <alvherre@alvh.no-ip.org>
Reviewed-by: Tom Lane <tgl@sss.pgh.pa.us>
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, ../../CACJufxG1NfL_7P31v1GHe5GamYE1J5OWxug36EG3rFt6swzG3w@mail.gmail.com/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 <jian.universality@gmail.com>
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/1075425.1732993688@sss.pgh.pa.us
---
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, ../../CACJufxG1NfL_7P31v1GHe5GamYE1J5OWxug36EG3rFt6swzG3w@mail.gmail.com/6-v9-0003-CREATE-SCHEMA-CREATE-COLLATION.patch)
download | inline diff:
From f585d3966cedf6046a863a7a9018a3ffd8399559 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
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] 55+ messages in thread
* Re: CREATE SCHEMA ... CREATE DOMAIN support
2024-11-12 12:54 CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-23 05:19 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-27 03:42 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-27 06:38 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-27 18:39 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-11-28 05:27 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-28 05:52 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-11-28 09:58 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-29 13:47 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-30 19:08 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-12-03 19:35 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Peter Eisentraut <peter@eisentraut.org>
2024-12-03 20:07 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-12-11 06:22 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-12-11 17:08 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2025-08-19 03:36 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2025-08-22 08:59 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2025-09-02 21:24 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2025-12-17 13:29 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2025-12-31 01:40 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2026-01-19 15:06 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
@ 2026-03-10 14:11 ` jian he <jian.universality@gmail.com>
2026-03-30 02:44 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
0 siblings, 1 reply; 55+ messages in thread
From: jian he @ 2026-03-10 14:11 UTC (permalink / raw)
To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Kirill Reshke <reshkekirill@gmail.com>; Peter Eisentraut <peter@eisentraut.org>; PostgreSQL Hackers <pgsql-hackers@lists.postgresql.org>
Hi.
Rebase only.
--
jian
https://www.enterprisedb.com/
Attachments:
[text/x-patch] v10-0004-CREATE-SCHEMA-CREATE-TYPE.patch (15.9K, ../../CACJufxFFWU2EGBAOt7Lp04gP84au5-rQ_EJKLEMbAi8p2Ch04Q@mail.gmail.com/2-v10-0004-CREATE-SCHEMA-CREATE-TYPE.patch)
download | inline diff:
From 3136cddd6a8762aacbf6c9cbce84db2300ab0ebc Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
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, ../../CACJufxFFWU2EGBAOt7Lp04gP84au5-rQ_EJKLEMbAi8p2Ch04Q@mail.gmail.com/3-v10-0003-CREATE-SCHEMA-CREATE-COLLATION.patch)
download | inline diff:
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
[text/x-patch] v10-0002-CREATE-SCHEMA-CREATE-DOMAIN.patch (12.6K, ../../CACJufxFFWU2EGBAOt7Lp04gP84au5-rQ_EJKLEMbAi8p2Ch04Q@mail.gmail.com/4-v10-0002-CREATE-SCHEMA-CREATE-DOMAIN.patch)
download | inline diff:
From 42c983e59f77197d99a584f7f7d63446fffa0752 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
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 <reshkekirill@gmail.com>
Author: Jian He <jian.universality@gmail.com>
Reviewed-by: Alvaro Herrera <alvherre@alvh.no-ip.org>
Reviewed-by: Tom Lane <tgl@sss.pgh.pa.us>
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, ../../CACJufxFFWU2EGBAOt7Lp04gP84au5-rQ_EJKLEMbAi8p2Ch04Q@mail.gmail.com/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 <jian.universality@gmail.com>
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 <tgl@sss.pgh.pa.us>
Discussion: https://postgr.es/m/1075425.1732993688@sss.pgh.pa.us
---
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, ../../CACJufxFFWU2EGBAOt7Lp04gP84au5-rQ_EJKLEMbAi8p2Ch04Q@mail.gmail.com/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 <jian.universality@gmail.com>
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/1075425.1732993688@sss.pgh.pa.us
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] 55+ messages in thread
* Re: CREATE SCHEMA ... CREATE DOMAIN support
2024-11-12 12:54 CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-23 05:19 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-27 03:42 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-27 06:38 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-27 18:39 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-11-28 05:27 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-28 05:52 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-11-28 09:58 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-29 13:47 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-30 19:08 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-12-03 19:35 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Peter Eisentraut <peter@eisentraut.org>
2024-12-03 20:07 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-12-11 06:22 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-12-11 17:08 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2025-08-19 03:36 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2025-08-22 08:59 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2025-09-02 21:24 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2025-12-17 13:29 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2025-12-31 01:40 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2026-01-19 15:06 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2026-03-10 14:11 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
@ 2026-03-30 02:44 ` jian he <jian.universality@gmail.com>
0 siblings, 0 replies; 55+ messages in thread
From: jian he @ 2026-03-30 02:44 UTC (permalink / raw)
To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Kirill Reshke <reshkekirill@gmail.com>; Peter Eisentraut <peter@eisentraut.org>; PostgreSQL Hackers <pgsql-hackers@lists.postgresql.org>
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, ../../CACJufxFn5RLZhcTNJjZs5Db8qEUJ5mkXN+UGcjTHXpb4OaoQSQ@mail.gmail.com/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 <jian.universality@gmail.com>
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/1075425.1732993688@sss.pgh.pa.us
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, ../../CACJufxFn5RLZhcTNJjZs5Db8qEUJ5mkXN+UGcjTHXpb4OaoQSQ@mail.gmail.com/3-v11-0002-CREATE-SCHEMA-CREATE-DOMAIN.patch)
download | inline diff:
From 2a3a621816ed96d5bad14e7d80403f72dc697b3b Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
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 <reshkekirill@gmail.com>
Author: Jian He <jian.universality@gmail.com>
Reviewed-by: Alvaro Herrera <alvherre@alvh.no-ip.org>
Reviewed-by: Tom Lane <tgl@sss.pgh.pa.us>
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, ../../CACJufxFn5RLZhcTNJjZs5Db8qEUJ5mkXN+UGcjTHXpb4OaoQSQ@mail.gmail.com/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 <jian.universality@gmail.com>
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 <tgl@sss.pgh.pa.us>
Discussion: https://postgr.es/m/1075425.1732993688@sss.pgh.pa.us
---
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, ../../CACJufxFn5RLZhcTNJjZs5Db8qEUJ5mkXN+UGcjTHXpb4OaoQSQ@mail.gmail.com/5-v11-0004-CREATE-SCHEMA-CREATE-TYPE.patch)
download | inline diff:
From 54f1c2c78ca2de4a325225569665de7d169bb485 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
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, ../../CACJufxFn5RLZhcTNJjZs5Db8qEUJ5mkXN+UGcjTHXpb4OaoQSQ@mail.gmail.com/6-v11-0003-CREATE-SCHEMA-CREATE-COLLATION.patch)
download | inline diff:
From 48cf5f3a3fc8fd17f712c47d49fc2464bb209527 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
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] 55+ messages in thread
* Re: CREATE SCHEMA ... CREATE DOMAIN support
2024-11-12 12:54 CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-23 05:19 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-27 03:42 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-27 06:38 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-27 18:39 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-11-28 05:27 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-28 05:52 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-11-28 09:58 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-29 13:47 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-30 19:08 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-12-03 19:35 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Peter Eisentraut <peter@eisentraut.org>
2024-12-03 20:07 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-12-11 06:22 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-12-11 17:08 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2025-08-19 03:36 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2025-08-22 08:59 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2025-09-02 21:24 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2025-12-17 13:29 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
@ 2026-04-03 18:35 ` Tom Lane <tgl@sss.pgh.pa.us>
2026-04-05 18:42 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
1 sibling, 1 reply; 55+ messages in thread
From: Tom Lane @ 2026-04-03 18:35 UTC (permalink / raw)
To: jian he <jian.universality@gmail.com>; +Cc: Kirill Reshke <reshkekirill@gmail.com>; Peter Eisentraut <peter@eisentraut.org>; PostgreSQL Hackers <pgsql-hackers@lists.postgresql.org>
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] 55+ messages in thread
* Re: CREATE SCHEMA ... CREATE DOMAIN support
2024-11-12 12:54 CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-23 05:19 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-27 03:42 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-27 06:38 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-27 18:39 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-11-28 05:27 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-28 05:52 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-11-28 09:58 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-29 13:47 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-30 19:08 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-12-03 19:35 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Peter Eisentraut <peter@eisentraut.org>
2024-12-03 20:07 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-12-11 06:22 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-12-11 17:08 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2025-08-19 03:36 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2025-08-22 08:59 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2025-09-02 21:24 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2025-12-17 13:29 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2026-04-03 18:35 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
@ 2026-04-05 18:42 ` Tom Lane <tgl@sss.pgh.pa.us>
2026-04-05 20:15 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
0 siblings, 1 reply; 55+ messages in thread
From: Tom Lane @ 2026-04-05 18:42 UTC (permalink / raw)
To: jian he <jian.universality@gmail.com>; +Cc: Kirill Reshke <reshkekirill@gmail.com>; Peter Eisentraut <peter@eisentraut.org>; PostgreSQL Hackers <pgsql-hackers@lists.postgresql.org>
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] 55+ messages in thread
* Re: CREATE SCHEMA ... CREATE DOMAIN support
2024-11-12 12:54 CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-23 05:19 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-27 03:42 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-27 06:38 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-27 18:39 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-11-28 05:27 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-28 05:52 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-11-28 09:58 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-29 13:47 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-30 19:08 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-12-03 19:35 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Peter Eisentraut <peter@eisentraut.org>
2024-12-03 20:07 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-12-11 06:22 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-12-11 17:08 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2025-08-19 03:36 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2025-08-22 08:59 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2025-09-02 21:24 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2025-12-17 13:29 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2026-04-03 18:35 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2026-04-05 18:42 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
@ 2026-04-05 20:15 ` Tom Lane <tgl@sss.pgh.pa.us>
2026-04-05 22:05 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2026-04-06 02:29 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
0 siblings, 2 replies; 55+ messages in thread
From: Tom Lane @ 2026-04-05 20:15 UTC (permalink / raw)
To: jian he <jian.universality@gmail.com>; +Cc: Kirill Reshke <reshkekirill@gmail.com>; Peter Eisentraut <peter@eisentraut.org>; PostgreSQL Hackers <pgsql-hackers@lists.postgresql.org>
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, ../../944307.1775420103@sss.pgh.pa.us/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 <tgl@sss.pgh.pa.us>
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 <tgl@sss.pgh.pa.us>
Reviewed-by: Kirill Reshke <reshkekirill@gmail.com>
Reviewed-by: jian he <jian.universality@gmail.com>
Discussion: https://postgr.es/m/1075425.1732993688@sss.pgh.pa.us
---
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, ../../944307.1775420103@sss.pgh.pa.us/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 <tgl@sss.pgh.pa.us>
Date: Sat, 4 Apr 2026 21:32:34 -0400
Subject: [PATCH v12 2/3] Execute foreign key constraints in CREATE SCHEMA at
the end.
The previous patch simplified CREATE SCHEMA's behavior to "execute all
subcommands in the order they are written". However, that's a bit too
simple, as the spec clearly requires forward references in foreign key
constraint clauses to work. (Most other SQL implementations seem to
read more into the spec than that, but it's not clear that there's
justification for more in the text, and this is the only case that
doesn't introduce unresolvable ambiguities.)
To fix that, transform FOREIGN KEY clauses into ALTER TABLE ... ADD
FOREIGN KEY commands and append them to the end of the CREATE SCHEMA's
subcommand list. This works because the foreign key constraints are
independent and don't affect any other DDL that might be in CREATE
SCHEMA. For simplicity, we do this for all FOREIGN KEY clauses even
if they would have worked where they were.
Author: jian he <jian.universality@gmail.com>
Reviewed-by: Tom Lane <tgl@sss.pgh.pa.us>
Discussion: https://postgr.es/m/1075425.1732993688@sss.pgh.pa.us
---
doc/src/sgml/ref/create_schema.sgml | 4 +
src/backend/parser/parse_utilcmd.c | 219 ++++++++++++++++--
.../expected/create_schema.out | 25 ++
.../test_ddl_deparse/sql/create_schema.sql | 11 +
src/test/regress/expected/create_schema.out | 46 ++++
src/test/regress/expected/event_trigger.out | 2 +-
src/test/regress/sql/create_schema.sql | 27 +++
7 files changed, 311 insertions(+), 23 deletions(-)
diff --git a/doc/src/sgml/ref/create_schema.sgml b/doc/src/sgml/ref/create_schema.sgml
index 32367c71b25..8e3c61890b8 100644
--- a/doc/src/sgml/ref/create_schema.sgml
+++ b/doc/src/sgml/ref/create_schema.sgml
@@ -201,6 +201,10 @@ CREATE VIEW hollywood.winners AS
external reference rather than a forward one.
Instead, <productname>PostgreSQL</productname> executes the subcommands
in <command>CREATE SCHEMA</command> in the order they are given.
+ The sole exception is that foreign key constraint clauses
+ in <command>CREATE TABLE</command> subcommands can be forward
+ references to later <command>CREATE TABLE</command> subcommands.
+ This allows circular foreign key references, which are sometimes useful.
</para>
<para>
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index cb2e57610b2..1bc5df7e0ef 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -122,11 +122,14 @@ static void transformFKConstraints(CreateStmtContext *cxt,
bool isAddConstraint);
static void transformCheckConstraints(CreateStmtContext *cxt,
bool skipValidation);
-static void transformConstraintAttrs(CreateStmtContext *cxt,
+static void transformConstraintAttrs(ParseState *pstate,
List *constraintList);
static void transformColumnType(CreateStmtContext *cxt, ColumnDef *column);
static void checkSchemaName(ParseState *pstate, const char *context_schema,
RangeVar *relation);
+static CreateStmt *transformCreateSchemaCreateTable(ParseState *pstate,
+ CreateStmt *stmt,
+ List **fk_elements);
static void transformPartitionCmd(CreateStmtContext *cxt, PartitionBoundSpec *bound);
static List *transformPartitionRangeBounds(ParseState *pstate, List *blist,
Relation parent);
@@ -693,7 +696,7 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
}
/* Process column constraints, if any... */
- transformConstraintAttrs(cxt, column->constraints);
+ transformConstraintAttrs(cxt->pstate, column->constraints);
/*
* First, scan the column's constraints to see if a not-null constraint
@@ -4194,9 +4197,12 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
* NOTE: currently, attributes are only supported for FOREIGN KEY, UNIQUE,
* EXCLUSION, and PRIMARY KEY constraints, but someday they ought to be
* supported for other constraint types.
+ *
+ * NOTE: this must be idempotent in non-error cases; see
+ * transformCreateSchemaCreateTable.
*/
static void
-transformConstraintAttrs(CreateStmtContext *cxt, List *constraintList)
+transformConstraintAttrs(ParseState *pstate, List *constraintList)
{
Constraint *lastprimarycon = NULL;
bool saw_deferrability = false;
@@ -4225,12 +4231,12 @@ transformConstraintAttrs(CreateStmtContext *cxt, List *constraintList)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("misplaced DEFERRABLE clause"),
- parser_errposition(cxt->pstate, con->location)));
+ parser_errposition(pstate, con->location)));
if (saw_deferrability)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("multiple DEFERRABLE/NOT DEFERRABLE clauses not allowed"),
- parser_errposition(cxt->pstate, con->location)));
+ parser_errposition(pstate, con->location)));
saw_deferrability = true;
lastprimarycon->deferrable = true;
break;
@@ -4240,12 +4246,12 @@ transformConstraintAttrs(CreateStmtContext *cxt, List *constraintList)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("misplaced NOT DEFERRABLE clause"),
- parser_errposition(cxt->pstate, con->location)));
+ parser_errposition(pstate, con->location)));
if (saw_deferrability)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("multiple DEFERRABLE/NOT DEFERRABLE clauses not allowed"),
- parser_errposition(cxt->pstate, con->location)));
+ parser_errposition(pstate, con->location)));
saw_deferrability = true;
lastprimarycon->deferrable = false;
if (saw_initially &&
@@ -4253,7 +4259,7 @@ transformConstraintAttrs(CreateStmtContext *cxt, List *constraintList)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("constraint declared INITIALLY DEFERRED must be DEFERRABLE"),
- parser_errposition(cxt->pstate, con->location)));
+ parser_errposition(pstate, con->location)));
break;
case CONSTR_ATTR_DEFERRED:
@@ -4261,12 +4267,12 @@ transformConstraintAttrs(CreateStmtContext *cxt, List *constraintList)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("misplaced INITIALLY DEFERRED clause"),
- parser_errposition(cxt->pstate, con->location)));
+ parser_errposition(pstate, con->location)));
if (saw_initially)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("multiple INITIALLY IMMEDIATE/DEFERRED clauses not allowed"),
- parser_errposition(cxt->pstate, con->location)));
+ parser_errposition(pstate, con->location)));
saw_initially = true;
lastprimarycon->initdeferred = true;
@@ -4279,7 +4285,7 @@ transformConstraintAttrs(CreateStmtContext *cxt, List *constraintList)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("constraint declared INITIALLY DEFERRED must be DEFERRABLE"),
- parser_errposition(cxt->pstate, con->location)));
+ parser_errposition(pstate, con->location)));
break;
case CONSTR_ATTR_IMMEDIATE:
@@ -4287,12 +4293,12 @@ transformConstraintAttrs(CreateStmtContext *cxt, List *constraintList)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("misplaced INITIALLY IMMEDIATE clause"),
- parser_errposition(cxt->pstate, con->location)));
+ parser_errposition(pstate, con->location)));
if (saw_initially)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("multiple INITIALLY IMMEDIATE/DEFERRED clauses not allowed"),
- parser_errposition(cxt->pstate, con->location)));
+ parser_errposition(pstate, con->location)));
saw_initially = true;
lastprimarycon->initdeferred = false;
break;
@@ -4304,12 +4310,12 @@ transformConstraintAttrs(CreateStmtContext *cxt, List *constraintList)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("misplaced ENFORCED clause"),
- parser_errposition(cxt->pstate, con->location)));
+ parser_errposition(pstate, con->location)));
if (saw_enforced)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("multiple ENFORCED/NOT ENFORCED clauses not allowed"),
- parser_errposition(cxt->pstate, con->location)));
+ parser_errposition(pstate, con->location)));
saw_enforced = true;
lastprimarycon->is_enforced = true;
break;
@@ -4321,12 +4327,12 @@ transformConstraintAttrs(CreateStmtContext *cxt, List *constraintList)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("misplaced NOT ENFORCED clause"),
- parser_errposition(cxt->pstate, con->location)));
+ parser_errposition(pstate, con->location)));
if (saw_enforced)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("multiple ENFORCED/NOT ENFORCED clauses not allowed"),
- parser_errposition(cxt->pstate, con->location)));
+ parser_errposition(pstate, con->location)));
saw_enforced = true;
lastprimarycon->is_enforced = false;
@@ -4384,12 +4390,17 @@ transformColumnType(CreateStmtContext *cxt, ColumnDef *column)
* transformCreateSchemaStmtElements -
* analyzes the elements of a CREATE SCHEMA statement
*
- * This is now somewhat vestigial: its only real responsibility is to complain
- * if any of the elements are trying to create objects outside the new schema.
+ * This presently has two responsibilities. We verify that no subcommands are
+ * trying to create objects outside the new schema. We also pull out any
+ * foreign-key constraint clauses embedded in CREATE TABLE subcommands, and
+ * convert them to ALTER TABLE ADD CONSTRAINT commands appended to the list.
+ * This supports forward references in foreign keys, which is required by the
+ * SQL standard.
+ *
* We used to try to re-order the commands in a way that would work even if
* the user-written order would not, but that's too hard (perhaps impossible)
* to do correctly with not-yet-parse-analyzed commands. Now we'll just
- * execute the elements in the order given.
+ * execute the elements in the order given, except for foreign keys.
*
* "schemaName" is the name of the schema that will be used for the creation
* of the objects listed. It may be obtained from the schema name defined
@@ -4398,12 +4409,17 @@ transformColumnType(CreateStmtContext *cxt, ColumnDef *column)
* The result is a list of parse nodes that still need to be analyzed ---
* but we can't analyze the later commands until we've executed the earlier
* ones, because of possible inter-object references.
+ *
+ * Note it's important that we not modify the input data structure. We create
+ * a new result List, and we copy any CREATE TABLE subcommands that we might
+ * modify.
*/
List *
transformCreateSchemaStmtElements(ParseState *pstate, List *schemaElts,
const char *schemaName)
{
List *elements = NIL;
+ List *fk_elements = NIL;
ListCell *lc;
/*
@@ -4430,7 +4446,11 @@ transformCreateSchemaStmtElements(ParseState *pstate, List *schemaElts,
CreateStmt *elp = (CreateStmt *) element;
checkSchemaName(pstate, schemaName, elp->relation);
- elements = lappend(elements, element);
+ /* Pull out any foreign key clauses, add to fk_elements */
+ elp = transformCreateSchemaCreateTable(pstate,
+ elp,
+ &fk_elements);
+ elements = lappend(elements, elp);
}
break;
@@ -4471,7 +4491,7 @@ transformCreateSchemaStmtElements(ParseState *pstate, List *schemaElts,
}
}
- return elements;
+ return list_concat(elements, fk_elements);
}
/*
@@ -4507,6 +4527,161 @@ checkSchemaName(ParseState *pstate, const char *context_schema,
}
}
+/*
+ * transformCreateSchemaCreateTable
+ * Process one CreateStmt for transformCreateSchemaStmtElements.
+ *
+ * We remove any foreign-key clauses in the statement and convert them into
+ * ALTER TABLE commands, which we append to *fk_elements.
+ */
+static CreateStmt *
+transformCreateSchemaCreateTable(ParseState *pstate,
+ CreateStmt *stmt,
+ List **fk_elements)
+{
+ CreateStmt *newstmt;
+ List *newElts = NIL;
+ ListCell *lc;
+
+ /*
+ * Flat-copy the CreateStmt node, allowing us to replace its tableElts
+ * list without damaging the input data structure. Most sub-nodes will be
+ * shared with the input, though.
+ */
+ newstmt = makeNode(CreateStmt);
+ memcpy(newstmt, stmt, sizeof(CreateStmt));
+
+ /* Scan for foreign-key constraints */
+ foreach(lc, stmt->tableElts)
+ {
+ Node *element = lfirst(lc);
+ AlterTableStmt *alterstmt;
+ AlterTableCmd *altercmd;
+
+ if (IsA(element, Constraint))
+ {
+ Constraint *constr = (Constraint *) element;
+
+ if (constr->contype != CONSTR_FOREIGN)
+ {
+ /* Other constraint types pass through unchanged */
+ newElts = lappend(newElts, constr);
+ continue;
+ }
+
+ /* Make it into an ALTER TABLE ADD CONSTRAINT command */
+ altercmd = makeNode(AlterTableCmd);
+ altercmd->subtype = AT_AddConstraint;
+ altercmd->name = NULL;
+ altercmd->def = (Node *) copyObject(constr);
+
+ alterstmt = makeNode(AlterTableStmt);
+ alterstmt->relation = copyObject(stmt->relation);
+ alterstmt->cmds = list_make1(altercmd);
+ alterstmt->objtype = OBJECT_TABLE;
+
+ *fk_elements = lappend(*fk_elements, alterstmt);
+ }
+ else if (IsA(element, ColumnDef))
+ {
+ ColumnDef *entry = (ColumnDef *) element;
+ ColumnDef *newentry;
+ List *entryconstraints;
+ bool afterFK = false;
+
+ /*
+ * We must preprocess the list of column constraints to attach
+ * attributes such as DEFERRED to the appropriate constraint node.
+ * Do this on a copy. (But execution of the CreateStmt will run
+ * transformConstraintAttrs on the copy, so we are nonetheless
+ * relying on transformConstraintAttrs to be idempotent.)
+ */
+ entryconstraints = copyObject(entry->constraints);
+ transformConstraintAttrs(pstate, entryconstraints);
+
+ /* Scan the column constraints ... */
+ foreach_node(Constraint, colconstr, entryconstraints)
+ {
+ switch (colconstr->contype)
+ {
+ case CONSTR_FOREIGN:
+ /* colconstr is already a copy, OK to modify */
+ colconstr->fk_attrs = list_make1(makeString(entry->colname));
+
+ /* Make it into an ALTER TABLE ADD CONSTRAINT command */
+ altercmd = makeNode(AlterTableCmd);
+ altercmd->subtype = AT_AddConstraint;
+ altercmd->name = NULL;
+ altercmd->def = (Node *) colconstr;
+
+ alterstmt = makeNode(AlterTableStmt);
+ alterstmt->relation = copyObject(stmt->relation);
+ alterstmt->cmds = list_make1(altercmd);
+ alterstmt->objtype = OBJECT_TABLE;
+
+ *fk_elements = lappend(*fk_elements, alterstmt);
+
+ /* Remove the Constraint node from entryconstraints */
+ entryconstraints =
+ foreach_delete_current(entryconstraints, colconstr);
+
+ /*
+ * Immediately-following attribute constraints should
+ * be dropped, too.
+ */
+ afterFK = true;
+ break;
+
+ /*
+ * Column constraint lists separate a Constraint node
+ * from its attributes (e.g. NOT ENFORCED); so a
+ * column-level foreign key constraint may be
+ * represented by multiple Constraint nodes. After
+ * transformConstraintAttrs, the foreign key
+ * Constraint node contains all required information,
+ * making it okay to put into *fk_elements as a
+ * stand-alone Constraint. But since we removed the
+ * foreign key Constraint node from entryconstraints,
+ * we must remove any dependent attribute nodes too,
+ * else the later re-execution of
+ * transformConstraintAttrs will misbehave.
+ */
+ case CONSTR_ATTR_DEFERRABLE:
+ case CONSTR_ATTR_NOT_DEFERRABLE:
+ case CONSTR_ATTR_DEFERRED:
+ case CONSTR_ATTR_IMMEDIATE:
+ case CONSTR_ATTR_ENFORCED:
+ case CONSTR_ATTR_NOT_ENFORCED:
+ if (afterFK)
+ entryconstraints =
+ foreach_delete_current(entryconstraints,
+ colconstr);
+ break;
+
+ default:
+ /* Any following constraint attributes are unrelated */
+ afterFK = false;
+ break;
+ }
+ }
+
+ /* Now make a modified ColumnDef to put into newElts */
+ newentry = makeNode(ColumnDef);
+ memcpy(newentry, entry, sizeof(ColumnDef));
+ newentry->constraints = entryconstraints;
+ newElts = lappend(newElts, newentry);
+ }
+ else
+ {
+ /* Other node types pass through unchanged */
+ newElts = lappend(newElts, element);
+ }
+ }
+
+ newstmt->tableElts = newElts;
+ return newstmt;
+}
+
/*
* transformPartitionCmd
* Analyze the ATTACH/DETACH/SPLIT PARTITION command
diff --git a/src/test/modules/test_ddl_deparse/expected/create_schema.out b/src/test/modules/test_ddl_deparse/expected/create_schema.out
index 8ab4eb03385..6ed85ef7446 100644
--- a/src/test/modules/test_ddl_deparse/expected/create_schema.out
+++ b/src/test/modules/test_ddl_deparse/expected/create_schema.out
@@ -17,3 +17,28 @@ CREATE SCHEMA element_test
NOTICE: DDL test: type simple, tag CREATE SCHEMA
NOTICE: DDL test: type simple, tag CREATE TABLE
NOTICE: DDL test: type simple, tag CREATE VIEW
+CREATE SCHEMA regress_schema_1
+CREATE TABLE t4(
+ b INT,
+ a INT REFERENCES t5 DEFERRABLE INITIALLY DEFERRED NOT ENFORCED
+ REFERENCES t6 DEFERRABLE INITIALLY DEFERRED,
+ CONSTRAINT fk FOREIGN KEY (a) REFERENCES t6 DEFERRABLE)
+CREATE TABLE t5 (a INT, b INT, PRIMARY KEY (a))
+CREATE TABLE t6 (a INT, b INT, PRIMARY KEY (a));
+NOTICE: DDL test: type simple, tag CREATE SCHEMA
+NOTICE: DDL test: type simple, tag CREATE TABLE
+NOTICE: DDL test: type simple, tag CREATE TABLE
+NOTICE: DDL test: type simple, tag CREATE INDEX
+NOTICE: DDL test: type simple, tag CREATE TABLE
+NOTICE: DDL test: type simple, tag CREATE INDEX
+NOTICE: DDL test: type alter table, tag ALTER TABLE
+NOTICE: subcommand: type ADD CONSTRAINT (and recurse) desc constraint t4_a_fkey on table regress_schema_1.t4
+NOTICE: DDL test: type alter table, tag ALTER TABLE
+NOTICE: subcommand: type ADD CONSTRAINT (and recurse) desc constraint t4_a_fkey1 on table regress_schema_1.t4
+NOTICE: DDL test: type alter table, tag ALTER TABLE
+NOTICE: subcommand: type ADD CONSTRAINT (and recurse) desc constraint fk on table regress_schema_1.t4
+DROP SCHEMA regress_schema_1 CASCADE;
+NOTICE: drop cascades to 3 other objects
+DETAIL: drop cascades to table regress_schema_1.t4
+drop cascades to table regress_schema_1.t5
+drop cascades to table regress_schema_1.t6
diff --git a/src/test/modules/test_ddl_deparse/sql/create_schema.sql b/src/test/modules/test_ddl_deparse/sql/create_schema.sql
index f314dc2b840..145aef2a75a 100644
--- a/src/test/modules/test_ddl_deparse/sql/create_schema.sql
+++ b/src/test/modules/test_ddl_deparse/sql/create_schema.sql
@@ -15,3 +15,14 @@ CREATE SCHEMA IF NOT EXISTS baz;
CREATE SCHEMA element_test
CREATE TABLE foo (id int)
CREATE VIEW bar AS SELECT * FROM foo;
+
+CREATE SCHEMA regress_schema_1
+CREATE TABLE t4(
+ b INT,
+ a INT REFERENCES t5 DEFERRABLE INITIALLY DEFERRED NOT ENFORCED
+ REFERENCES t6 DEFERRABLE INITIALLY DEFERRED,
+ CONSTRAINT fk FOREIGN KEY (a) REFERENCES t6 DEFERRABLE)
+CREATE TABLE t5 (a INT, b INT, PRIMARY KEY (a))
+CREATE TABLE t6 (a INT, b INT, PRIMARY KEY (a));
+
+DROP SCHEMA regress_schema_1 CASCADE;
diff --git a/src/test/regress/expected/create_schema.out b/src/test/regress/expected/create_schema.out
index 4ab947a60a8..b34b9988962 100644
--- a/src/test/regress/expected/create_schema.out
+++ b/src/test/regress/expected/create_schema.out
@@ -131,5 +131,51 @@ CREATE SCHEMA regress_schema_1 AUTHORIZATION CURRENT_ROLE
DROP SCHEMA regress_schema_1 CASCADE;
NOTICE: drop cascades to table regress_schema_1.tab
RESET ROLE;
+-- Test forward-referencing foreign key clauses.
+CREATE SCHEMA regress_schema_fk
+ CREATE TABLE regress_schema_fk.t2 (
+ b int,
+ a int REFERENCES t1 DEFERRABLE INITIALLY DEFERRED NOT ENFORCED
+ REFERENCES t3 DEFERRABLE INITIALLY DEFERRED,
+ CONSTRAINT fk FOREIGN KEY (a) REFERENCES t1 NOT DEFERRABLE)
+ CREATE TABLE regress_schema_fk.t1 (a int PRIMARY KEY)
+ CREATE TABLE t3 (a int PRIMARY KEY)
+ CREATE TABLE t4 (
+ b int,
+ a int REFERENCES t5 NOT DEFERRABLE ENFORCED
+ REFERENCES t6 DEFERRABLE INITIALLY IMMEDIATE,
+ CONSTRAINT fk FOREIGN KEY (a) REFERENCES t6 DEFERRABLE INITIALLY DEFERRED)
+ CREATE TABLE t5 (a int, b int, PRIMARY KEY (a))
+ CREATE TABLE t6 (a int, b int, PRIMARY KEY (a));
+\d regress_schema_fk.t2
+ Table "regress_schema_fk.t2"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ b | integer | | |
+ a | integer | | |
+Foreign-key constraints:
+ "fk" FOREIGN KEY (a) REFERENCES regress_schema_fk.t1(a)
+ "t2_a_fkey" FOREIGN KEY (a) REFERENCES regress_schema_fk.t1(a) DEFERRABLE INITIALLY DEFERRED NOT ENFORCED
+ "t2_a_fkey1" FOREIGN KEY (a) REFERENCES regress_schema_fk.t3(a) DEFERRABLE INITIALLY DEFERRED
+
+\d regress_schema_fk.t4
+ Table "regress_schema_fk.t4"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ b | integer | | |
+ a | integer | | |
+Foreign-key constraints:
+ "fk" FOREIGN KEY (a) REFERENCES regress_schema_fk.t6(a) DEFERRABLE INITIALLY DEFERRED
+ "t4_a_fkey" FOREIGN KEY (a) REFERENCES regress_schema_fk.t5(a)
+ "t4_a_fkey1" FOREIGN KEY (a) REFERENCES regress_schema_fk.t6(a) DEFERRABLE
+
+DROP SCHEMA regress_schema_fk CASCADE;
+NOTICE: drop cascades to 6 other objects
+DETAIL: drop cascades to table regress_schema_fk.t2
+drop cascades to table regress_schema_fk.t1
+drop cascades to table regress_schema_fk.t3
+drop cascades to table regress_schema_fk.t4
+drop cascades to table regress_schema_fk.t5
+drop cascades to table regress_schema_fk.t6
-- Clean up
DROP ROLE regress_create_schema_role;
diff --git a/src/test/regress/expected/event_trigger.out b/src/test/regress/expected/event_trigger.out
index 4c32e1dcaf5..065f586310f 100644
--- a/src/test/regress/expected/event_trigger.out
+++ b/src/test/regress/expected/event_trigger.out
@@ -427,11 +427,11 @@ NOTICE: END: command_tag=ALTER SEQUENCE type=sequence identity=evttrig.one_col_
NOTICE: END: command_tag=ALTER SEQUENCE type=sequence identity=evttrig.one_col_c_seq
NOTICE: END: command_tag=CREATE INDEX type=index identity=evttrig.one_idx
NOTICE: END: command_tag=CREATE TABLE type=table identity=evttrig.two
-NOTICE: END: command_tag=ALTER TABLE type=table identity=evttrig.two
NOTICE: END: command_tag=CREATE SEQUENCE type=sequence identity=evttrig.id_col_d_seq
NOTICE: END: command_tag=CREATE TABLE type=table identity=evttrig.id
NOTICE: END: command_tag=ALTER SEQUENCE type=sequence identity=evttrig.id_col_d_seq
NOTICE: END: command_tag=CREATE VIEW type=view identity=evttrig.one_view
+NOTICE: END: command_tag=ALTER TABLE type=table identity=evttrig.two
-- View with column additions
CREATE OR REPLACE VIEW evttrig.one_view AS SELECT * FROM evttrig.two, evttrig.id;
NOTICE: END: command_tag=CREATE VIEW type=view identity=evttrig.one_view
diff --git a/src/test/regress/sql/create_schema.sql b/src/test/regress/sql/create_schema.sql
index 62651342114..0f2accc59ec 100644
--- a/src/test/regress/sql/create_schema.sql
+++ b/src/test/regress/sql/create_schema.sql
@@ -71,5 +71,32 @@ CREATE SCHEMA regress_schema_1 AUTHORIZATION CURRENT_ROLE
DROP SCHEMA regress_schema_1 CASCADE;
RESET ROLE;
+-- Test forward-referencing foreign key clauses.
+CREATE SCHEMA regress_schema_fk
+ CREATE TABLE regress_schema_fk.t2 (
+ b int,
+ a int REFERENCES t1 DEFERRABLE INITIALLY DEFERRED NOT ENFORCED
+ REFERENCES t3 DEFERRABLE INITIALLY DEFERRED,
+ CONSTRAINT fk FOREIGN KEY (a) REFERENCES t1 NOT DEFERRABLE)
+
+ CREATE TABLE regress_schema_fk.t1 (a int PRIMARY KEY)
+
+ CREATE TABLE t3 (a int PRIMARY KEY)
+
+ CREATE TABLE t4 (
+ b int,
+ a int REFERENCES t5 NOT DEFERRABLE ENFORCED
+ REFERENCES t6 DEFERRABLE INITIALLY IMMEDIATE,
+ CONSTRAINT fk FOREIGN KEY (a) REFERENCES t6 DEFERRABLE INITIALLY DEFERRED)
+
+ CREATE TABLE t5 (a int, b int, PRIMARY KEY (a))
+
+ CREATE TABLE t6 (a int, b int, PRIMARY KEY (a));
+
+\d regress_schema_fk.t2
+\d regress_schema_fk.t4
+
+DROP SCHEMA regress_schema_fk CASCADE;
+
-- Clean up
DROP ROLE regress_create_schema_role;
--
2.43.7
[text/x-diff] v12-0003-Support-more-object-types-within-CREATE-SCHEMA.patch (25.7K, ../../944307.1775420103@sss.pgh.pa.us/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 <tgl@sss.pgh.pa.us>
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 <reshkekirill@gmail.com>
Author: Jian He <jian.universality@gmail.com>
Reviewed-by: Tom Lane <tgl@sss.pgh.pa.us>
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] 55+ messages in thread
* Re: CREATE SCHEMA ... CREATE DOMAIN support
2024-11-12 12:54 CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-23 05:19 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-27 03:42 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-27 06:38 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-27 18:39 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-11-28 05:27 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-28 05:52 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-11-28 09:58 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-29 13:47 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-30 19:08 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-12-03 19:35 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Peter Eisentraut <peter@eisentraut.org>
2024-12-03 20:07 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-12-11 06:22 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-12-11 17:08 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2025-08-19 03:36 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2025-08-22 08:59 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2025-09-02 21:24 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2025-12-17 13:29 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2026-04-03 18:35 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2026-04-05 18:42 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2026-04-05 20:15 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
@ 2026-04-05 22:05 ` Tom Lane <tgl@sss.pgh.pa.us>
1 sibling, 0 replies; 55+ messages in thread
From: Tom Lane @ 2026-04-05 22:05 UTC (permalink / raw)
To: jian he <jian.universality@gmail.com>; +Cc: Kirill Reshke <reshkekirill@gmail.com>; Peter Eisentraut <peter@eisentraut.org>; PostgreSQL Hackers <pgsql-hackers@lists.postgresql.org>
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, ../../1105374.1775426741@sss.pgh.pa.us/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 <tgl@sss.pgh.pa.us>
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 <tgl@sss.pgh.pa.us>
Reviewed-by: Kirill Reshke <reshkekirill@gmail.com>
Reviewed-by: jian he <jian.universality@gmail.com>
Discussion: https://postgr.es/m/1075425.1732993688@sss.pgh.pa.us
---
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, ../../1105374.1775426741@sss.pgh.pa.us/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 <tgl@sss.pgh.pa.us>
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 <jian.universality@gmail.com>
Reviewed-by: Tom Lane <tgl@sss.pgh.pa.us>
Discussion: https://postgr.es/m/1075425.1732993688@sss.pgh.pa.us
---
doc/src/sgml/ref/create_schema.sgml | 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, ../../1105374.1775426741@sss.pgh.pa.us/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 <tgl@sss.pgh.pa.us>
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 <reshkekirill@gmail.com>
Author: Jian He <jian.universality@gmail.com>
Reviewed-by: Tom Lane <tgl@sss.pgh.pa.us>
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] 55+ messages in thread
* Re: CREATE SCHEMA ... CREATE DOMAIN support
2024-11-12 12:54 CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-23 05:19 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-27 03:42 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-27 06:38 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-27 18:39 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-11-28 05:27 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-28 05:52 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-11-28 09:58 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-29 13:47 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-30 19:08 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-12-03 19:35 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Peter Eisentraut <peter@eisentraut.org>
2024-12-03 20:07 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-12-11 06:22 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-12-11 17:08 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2025-08-19 03:36 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2025-08-22 08:59 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2025-09-02 21:24 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2025-12-17 13:29 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2026-04-03 18:35 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2026-04-05 18:42 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2026-04-05 20:15 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
@ 2026-04-06 02:29 ` jian he <jian.universality@gmail.com>
2026-04-06 03:04 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
1 sibling, 1 reply; 55+ messages in thread
From: jian he @ 2026-04-06 02:29 UTC (permalink / raw)
To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Kirill Reshke <reshkekirill@gmail.com>; Peter Eisentraut <peter@eisentraut.org>; PostgreSQL Hackers <pgsql-hackers@lists.postgresql.org>
On Mon, Apr 6, 2026 at 4:15 AM Tom Lane <tgl@sss.pgh.pa.us> 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] 55+ messages in thread
* Re: CREATE SCHEMA ... CREATE DOMAIN support
2024-11-12 12:54 CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-23 05:19 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-27 03:42 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-27 06:38 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-27 18:39 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-11-28 05:27 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-28 05:52 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-11-28 09:58 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-29 13:47 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-30 19:08 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-12-03 19:35 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Peter Eisentraut <peter@eisentraut.org>
2024-12-03 20:07 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-12-11 06:22 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-12-11 17:08 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2025-08-19 03:36 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2025-08-22 08:59 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2025-09-02 21:24 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2025-12-17 13:29 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2026-04-03 18:35 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2026-04-05 18:42 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2026-04-05 20:15 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2026-04-06 02:29 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
@ 2026-04-06 03:04 ` Tom Lane <tgl@sss.pgh.pa.us>
2026-04-06 19:19 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
0 siblings, 1 reply; 55+ messages in thread
From: Tom Lane @ 2026-04-06 03:04 UTC (permalink / raw)
To: jian he <jian.universality@gmail.com>; +Cc: Kirill Reshke <reshkekirill@gmail.com>; Peter Eisentraut <peter@eisentraut.org>; PostgreSQL Hackers <pgsql-hackers@lists.postgresql.org>
jian he <jian.universality@gmail.com> writes:
> On Mon, Apr 6, 2026 at 4:15 AM Tom Lane <tgl@sss.pgh.pa.us> 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] 55+ messages in thread
* Re: CREATE SCHEMA ... CREATE DOMAIN support
2024-11-12 12:54 CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-23 05:19 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-27 03:42 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-27 06:38 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-27 18:39 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-11-28 05:27 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-28 05:52 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-11-28 09:58 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-29 13:47 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-30 19:08 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-12-03 19:35 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Peter Eisentraut <peter@eisentraut.org>
2024-12-03 20:07 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-12-11 06:22 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-12-11 17:08 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2025-08-19 03:36 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2025-08-22 08:59 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2025-09-02 21:24 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2025-12-17 13:29 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2026-04-03 18:35 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2026-04-05 18:42 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2026-04-05 20:15 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2026-04-06 02:29 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2026-04-06 03:04 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
@ 2026-04-06 19:19 ` Tom Lane <tgl@sss.pgh.pa.us>
2026-09-04 00:29 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Noah Misch <noah@leadboat.com>
0 siblings, 1 reply; 55+ messages in thread
From: Tom Lane @ 2026-04-06 19:19 UTC (permalink / raw)
To: jian he <jian.universality@gmail.com>; +Cc: Kirill Reshke <reshkekirill@gmail.com>; Peter Eisentraut <peter@eisentraut.org>; PostgreSQL Hackers <pgsql-hackers@lists.postgresql.org>
I pushed v13 after a tiny bit of additional tweaking.
regards, tom lane
^ permalink raw reply [nested|flat] 55+ messages in thread
* Re: CREATE SCHEMA ... CREATE DOMAIN support
2024-11-12 12:54 CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-23 05:19 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-27 03:42 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-27 06:38 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-27 18:39 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-11-28 05:27 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-28 05:52 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-11-28 09:58 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-29 13:47 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-30 19:08 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-12-03 19:35 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Peter Eisentraut <peter@eisentraut.org>
2024-12-03 20:07 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-12-11 06:22 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-12-11 17:08 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2025-08-19 03:36 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2025-08-22 08:59 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2025-09-02 21:24 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2025-12-17 13:29 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2026-04-03 18:35 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2026-04-05 18:42 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2026-04-05 20:15 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2026-04-06 02:29 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2026-04-06 03:04 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2026-04-06 19:19 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
@ 2026-09-04 00:29 ` Noah Misch <noah@leadboat.com>
2026-09-04 01:00 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
0 siblings, 1 reply; 55+ messages in thread
From: Noah Misch @ 2026-09-04 00:29 UTC (permalink / raw)
To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: jian he <jian.universality@gmail.com>; Kirill Reshke <reshkekirill@gmail.com>; Peter Eisentraut <peter@eisentraut.org>; PostgreSQL Hackers <pgsql-hackers@lists.postgresql.org>
On Mon, Apr 06, 2026 at 03:19:48PM -0400, Tom Lane wrote:
> I pushed v13 after a tiny bit of additional tweaking.
I had Opus 5 review d516974 "Support more object types within CREATE SCHEMA".
It raised a problem in a9c350d "Don't try to re-order the subcommands of
CREATE SCHEMA":
> +## 4. Findings that belong to the sibling commit a9c350d, not to d516974
> +The common cause: `a9c350d` deleted `setSchemaName()`, which used to write the new schema's
> +name into each element's `RangeVar->schemaname` [...] From then on the new schema
> +reaches its own elements solely through a prepended `search_path` entry
> + [...] ISO/IEC 9075-2 5.4 SR 4)b) (found.txt:8701-8702) reads verbatim
> +"If the <local or schema qualified name> is contained in a <schema definition>, then the
> +<schema name> that is specified or implicit in the <schema definition> is implicit."
The full review, attached, contains several examples. Some are exotic.
Here's one that resonates more with me, adapted slightly from the a9c350d
commit message:
CREATE TABLE public.mytable (c int);
CREATE SCHEMA myschema
CREATE VIEW myview AS SELECT * FROM mytable
CREATE TABLE mytable (c int);
SET search_path = pg_catalog;
\d+ myschema.myview
RESET search_path;
In v18, myview binds to myschema.mytable. In v19, it binds to public.mytable.
I think the above-described syntax rule about <local or schema qualified name>
doesn't allow v19's interpretation.
Stepping back, I am concerned about the complexity v19 is incurring to extend
CREATE SCHEMA ... CREATE. The syntax gives only modest benefits, since you
can achieve the same object creation with freestanding SQL statements. The
above example is indicative of the unusual semantic challenges. CREATE SCHEMA
... CREATE also handicaps the parsing of every command it supports, since
parsing can't rely on semicolons to find command boundaries. I grant this
project closed one gap in PostgreSQL SQL standard compliance. I'm concerned
about painting ourselves into a corner with respect to future syntax needs.
Here are my notes on the d516974 findings in the report, only one of which I
consider a non-negligible bug in d516974.
> +### D1 — psql (and pgbench) do not end a `CREATE SCHEMA` at its semicolon when a `GRANT` clause follows a routine clause
This title overstates the connection to $SUBJECT. As the report goes on to
explain, this is a variant of an older bug of psql detecting "begin" in the
wrong contexts:
-- works
CREATE FUNCTION x1f() RETURNS int LANGUAGE sql SET search_path = "begin" AS 'select 1';
select 'ended';
-- psql wrongly concludes command is not yet terminated
CREATE FUNCTION x1f() RETURNS int LANGUAGE sql SET search_path = begin AS 'select 1';
select 'ended';
This doesn't affect pg_dump, fortunately, which always quotes the GUC value.
> +### D2 — a `SET role` / `SET session_authorization` clause on a routine element is refused under `AUTHORIZATION`, naming a security-definer function that does not exist
I would not act on this.
> +### D3 — PL/pgSQL cannot contain a `CREATE SCHEMA` whose element is a `BEGIN ATOMIC` routine
I think this does qualify as a bug in d516974, but it's not very concerning.
> +### D4 — psql offers `COLLATION` and `TYPE` as `CREATE SCHEMA` clauses but does not complete them
I would not act on this.
commit 8a98da0 (HEAD, cqla/create-schema-defect-tests)
Author: Noah Misch <noah@leadboat.com>
AuthorDate: Thu Sep 3 03:43:31 2026 +0000
Commit: Noah Misch <noah@leadboat.com>
CommitDate: Thu Sep 3 03:43:31 2026 +0000
Audit of d516974 (more object types in CREATE SCHEMA): report and tests
Automated, model-driven audit of commit d516974 ("Support more object types
within CREATE SCHEMA.") for user-visible defects still present in master
(6885b84). Everything here was written by a language model and has had no
human review; see PROVENANCE.md.
CREATE_SCHEMA_DEFECT_REPORT.md reports four defects attributable to d516974,
none severe:
D1 psql (and pgbench) do not end a CREATE SCHEMA at its semicolon when a
GRANT clause follows a CREATE FUNCTION/PROCEDURE clause: the routine
clause's BEGIN/END tracking state leaks into the GRANT, which never
resets it because GRANT is not introduced by CREATE. The statement is
merged with everything after it, so a following VACUUM fails with
"cannot run inside a transaction block" and later statements never run.
D2 A SET role / SET session_authorization clause on a routine element is
refused under AUTHORIZATION <other role>, with a message naming a
security-definer function that does not exist.
D3 PL/pgSQL cannot contain a CREATE SCHEMA whose element is a BEGIN ATOMIC
routine; pl_gram.y still uses psql's pre-049b742 heuristic.
D4 psql offers COLLATION and TYPE as CREATE SCHEMA clauses but then does not
complete them.
The report also has sections for findings belonging to the sibling commit
a9c350d rather than to d516974, for what was examined and found correct, and
for the 16 of 19 candidates that were investigated and rejected -- most because
the same behavior reproduces outside CREATE SCHEMA entirely, on builds that
predate the whole series.
There is no fix here, so the added tests encode what a correct implementation
would print and therefore FAIL on master by design; the failure diff is the
demonstration. Only test files are touched. With the additions, regress
(create_schema), plpgsql (plpgsql_misc), psql/001_basic subtests 135-137 and
psql/010_tab_completion subtests 110-111 fail, each only on the new material,
and three further meson tests fail collaterally because they run the core
parallel_schedule. Every added control passes. With the six test files
stashed, all six suites are green (243+13+134+101+2+5 subtests, 0 failures).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AfvwDcK3tFM4H1JeQXz2Jz
---
CREATE_SCHEMA_DEFECT_REPORT.md | 1203 ++++++++++++++++++++++++++
PROVENANCE.md | 305 +++++++
src/bin/psql/t/001_basic.pl | 40 +
src/bin/psql/t/010_tab_completion.pl | 95 ++
src/pl/plpgsql/src/expected/plpgsql_misc.out | 35 +
src/pl/plpgsql/src/sql/plpgsql_misc.sql | 31 +
src/test/regress/expected/create_schema.out | 78 ++
src/test/regress/sql/create_schema.sql | 60 ++
8 files changed, 1847 insertions(+)
diff --git a/CREATE_SCHEMA_DEFECT_REPORT.md b/CREATE_SCHEMA_DEFECT_REPORT.md
new file mode 100644
index 0000000..cabc4f3
--- /dev/null
+++ b/CREATE_SCHEMA_DEFECT_REPORT.md
@@ -0,0 +1,1203 @@
+# Audit of d516974 — "Support more object types within CREATE SCHEMA."
+
+## Verdict
+
+Four user-visible defects attributable to commit `d516974840f4059d331ae6057ede3e4edd3c6747`
+survive in master (`6885b845b4ba0b7aee09daa9817703477faa3704`), and none of them is severe.
+Two are in psql — its statement lexer merges a `CREATE SCHEMA` with everything after it when
+a `GRANT` clause follows a routine clause (D1), and its tab completion offers `COLLATION` and
+`TYPE` as `CREATE SCHEMA` clauses but then does not complete them (D4). One is in PL/pgSQL's
+own statement lexer, which cannot express a `CREATE SCHEMA` whose element is a `BEGIN ATOMIC`
+routine (D3). One is server-side: a `SET role` / `SET session_authorization` clause on a
+routine element is refused under `AUTHORIZATION` with a message naming a security-definer
+function that does not exist (D2).
+The server-side heart of the commit is in good shape: the newly-allowed sub-commands
+produce catalog contents, dependencies, ownership, event-trigger payloads, error positions
+and `pg_dump` output that match the same commands issued separately under an equivalent
+`search_path` — byte for byte, apart from pg_dump's per-dump `\restrict` nonce — and 16 hunt
+lenses plus a refute stage found no crash, no assertion failure, no privilege escalation and
+no data corruption *attributable* to the new syntax. (Two hazards of that kind are reachable
+*through* it and are reported below because they matter, but neither is caused by it: the
+confused deputy of §4 S2, and the dangling-`pronamespace` catalog corruption of §6 C06 —
+each reproduces on `pre` with no `CREATE SCHEMA` element list involved at all.)
+The most serious problems this audit found in the CREATE SCHEMA area — the `"$user"` schema
+that receives none of its own elements, the session temp schema outranking the new schema,
+and an element's user code hijacking the temporary `search_path` — all belong to the sibling
+commit **a9c350d**, not to d516974; they are reported in §4 as context. Sixteen of the nineteen
+hunt candidates were rejected outright, together with orchestrator findings O2 and O4 (§6) —
+most because the same behaviour reproduces outside `CREATE SCHEMA` entirely, on builds that
+predate the whole series.
+
+---
+
+## 1. Scope
+
+**Commit audited:** `d516974840f4059d331ae6057ede3e4edd3c6747`, Tom Lane, 2026-04-06,
+"Support more object types within CREATE SCHEMA." It adds `CreateDomainStmt`,
+`CreateFunctionStmt` and `DefineStmt` to the `schema_stmt` nonterminal
+(`src/backend/parser/gram.y:1686-1696`), adds `checkSchemaNameList()` and the new node cases
+in `transformCreateSchemaStmtElements()` (`src/backend/parser/parse_utilcmd.c`), tweaks
+psql tab completion and the psql lexer, and extends `create_schema.sgml`. Because the
+grammar's `DefineStmt` also produces `CompositeTypeStmt`, `CreateEnumStmt` and
+`CreateRangeStmt`, the newly-legal clauses are: `CREATE [OR REPLACE] FUNCTION`/`PROCEDURE`,
+`CREATE DOMAIN`, `CREATE [OR REPLACE] AGGREGATE` (modern and pre-8.2 syntax),
+`CREATE OPERATOR`, `CREATE TYPE` (shell, base, `AS (...)`, `AS ENUM`, `AS RANGE`),
+`CREATE COLLATION` (definition / `FROM` / `IF NOT EXISTS`) and
+`CREATE TEXT SEARCH PARSER | DICTIONARY | TEMPLATE | CONFIGURATION`.
+
+**Base commit:** `404db8f9edbb` (= `d516974^`).
+
+**Sibling commits, in the same series, NOT audited here:**
+`a9c350d9ee66` "Don't try to re-order the subcommands of CREATE SCHEMA." (= the commit that
+replaced per-element schema-name rewriting with a temporarily prepended `search_path`), and
+`404db8f9edbb` "Execute foreign key constraints in CREATE SCHEMA at the end."
+`a9c350d^` is `1ff3180ca016`. One later follow-up already in master:
+`049b742daad0` (2026-06-23) "psql: Tighten heuristics for BEGIN/END within CREATE SCHEMA."
+
+### What "defect" means in this report
+
+Every candidate was scored on three independent booleans, and a candidate is a defect iff
+`per_principles OR (sql_mismatch AND is_new)`:
+
+* **`sql_mismatch`** — the SQL standard requires behaviour master does not exhibit. Text
+ consulted: ISO/IEC 9075-2:200x committee draft at `/home/nm/src/pg/gbyrun/std/found.txt`.
+ No standard requirement is asserted in this report without a verbatim quotation.
+* **`per_principles`** — the commit's *own* principles require behaviour master does not
+ exhibit: its commit message, the code comments it wrote, the documentation it wrote, and
+ the plain proposition that a command `create_schema.sgml` lists as accepted inside
+ `CREATE SCHEMA` should behave inside `CREATE SCHEMA` the way it behaves outside, apart
+ from landing in the new schema. Crashes, assertion failures, wrong catalog contents,
+ silently-wrong results and misleading error messages are always violations.
+* **`is_new`** — the *rule violation* (not merely the syntax) is new relative to `d516974^`.
+ New syntax alone is not a defect.
+
+Attribution keys on the rule violation, not on which file the commit touched. A finding
+that reproduces on `old` with a pre-d516974-legal element, or outside `CREATE SCHEMA`
+entirely, is not a d516974 finding; those are in §4 and §6.
+
+### Reference builds
+
+| name | commit | meaning | server version |
+|---|---|---|---|
+| `new` | `6885b845b4ba` | master, **with** d516974 | 20devel |
+| `old` | `404db8f9edbb` | `d516974^`, with a9c350d and 404db8f | 19devel |
+| `pre` | `1ff3180ca016` | `a9c350d^`, before the whole series | 19devel |
+
+All three are meson builds with `cassert` and debug. `a9c350d` is an ancestor of `404db8f`;
+`d516974` is an ancestor of master; `git rev-list --count 404db8f..6885b84` = **1271**, so no
+`is_new` claim below rests on the new-vs-old behavioural delta alone — each is anchored in
+`git blame`/source or in a probe using pre-d516974 syntax.
+
+---
+
+## 2. Findings attributable to d516974
+
+Severity order. None is a crash, an assertion failure, a privilege escalation, or a source
+of catalog corruption; the cassert server logs are clean (`PANIC|FATAL|TRAP|Assert`) on every
+run cited in this report.
+
+---
+
+### D1 — psql (and pgbench) do not end a `CREATE SCHEMA` at its semicolon when a `GRANT` clause follows a routine clause
+
+**Severity: low-to-medium. New in d516974 (narrowed, not fixed, by 049b742).**
+
+**One-line statement.** A `CREATE [OR REPLACE] FUNCTION`/`PROCEDURE` clause arms
+`psqlscan.l`'s BEGIN/END counter; the per-clause reset that is supposed to disarm it fires
+only on the token `create`, and `GrantStmt` is the one `schema_stmt` alternative that does
+not begin with `CREATE` — so an unquoted identifier `begin` inside a following `GRANT`
+clause is counted as the start of a routine body, and psql sends the `CREATE SCHEMA` and
+everything after it as a single query.
+
+**Minimal reproduction** (build `new`; no role setup needed — the poisoning identifier is a
+view the same statement creates):
+
+```sql
+CREATE SCHEMA cs_gb
+ CREATE VIEW begin AS SELECT 1 AS one
+ CREATE FUNCTION cs_gb_f() RETURNS int LANGUAGE sql AS 'select 1'
+ GRANT SELECT ON begin TO public;
+VACUUM;
+SELECT 'split ok' AS result;
+```
+
+Run as `psql -X -q -A -t -v ON_ERROR_STOP=1 -f defect.sql`.
+
+**Literal observed output** (`/home/nm/src/pg/csxrun/evidence/CLIENT-C03-oracle-new.txt`):
+
+```
+############ CASE: defect ############
+psql:/tmp/tmp.vkud8hwplT/defect.sql:6: ERROR: VACUUM cannot run inside a transaction block
+psql exit=3
+-- schemas that exist now --
+
+```
+
+The blank line is the point: `cs_gb` does not exist. The script contains no transaction
+block. The server confirms the merge — with `log_statement=all` a *single*
+`LOG: statement:` entry carries all of the input
+(`/home/nm/src/pg/csxrun/evidence/ADJ-C03-A-new.txt`, `refute-C03-run2-allbuilds.txt`).
+
+**What a correct implementation produces, and why.** Changing nothing but the quoting of
+the same view name — `GRANT SELECT ON "begin" TO public` — is semantically identical SQL
+(`begin` is an unreserved keyword) and lexes correctly. Same file, same run:
+
+```
+############ CASE: oracle ############
+split ok
+psql exit=0
+-- schemas that exist now --
+cs_gb_oracle
+```
+
+so the oracle is master's own behaviour one quoting change away. The control — same shape
+with `CREATE VIEW` in place of the routine clause — also splits correctly (`CASE: control`
+in the same transcript, `cs_gc` created).
+
+**The worst symptom is in an interactive terminal, where there is no diagnostic at all.**
+Real pty, `--no-psqlrc`, `INPUTRC=/dev/null`
+(`/home/nm/src/pg/csxrun/evidence/CRIT-3-interactive-new.txt`, build `new`):
+
+```
+----- typed:
+CREATE SCHEMA cs1
+ CREATE FUNCTION f() RETURNS int LANGUAGE sql AS 'select 1'
+ GRANT ALL ON SCHEMA cs1 TO begin;
+SELECT 'MARKER_P' AS m;
+\echo ZZ_PROBE_END
+----- psql showed:
+CREATE SCHEMA cs1
+postgres-# CREATE FUNCTION f() RETURNS int LANGUAGE sql AS 'select 1'
+postgres-# GRANT ALL ON SCHEMA cs1 TO begin;
+postgres-# SELECT 'MARKER_P' AS m;
+postgres-# \echo ZZ_PROBE_END
+ZZ_PROBE_END
+postgres-#
+```
+
+The user typed a complete, semicolon-terminated statement; psql printed nothing, sent
+nothing to the server, and silently sat on the continuation prompt. Every later statement
+is swallowed the same way. The only exits are `\r` (which discards everything typed since
+the wedge) or `\g`. On `old` the identical keystrokes return the prompt to `postgres=#` and
+run the next statement (`CRIT-3-interactive-old.txt`). This consequence was found by the
+completeness critic and did **not** pass through the refute stage; it was not run on `pre`.
+
+**Mechanism** (against 6885b84).
+
+* `src/fe_utils/psqlscan.l:1102-1107` — the per-clause reset:
+ `if (pg_strcasecmp(identifier, "create") == 0) { state->sub_idents_count = 0; ... }`.
+* `src/backend/parser/gram.y:1686-1696` — `GrantStmt` is the only `schema_stmt` alternative
+ that does not begin with `CREATE`, so the reset never fires for a `GRANT` clause and
+ `sub_idents` still spells `c`,`f` from the preceding routine clause.
+* `src/fe_utils/psqlscan.l:1118-1123` — with `psqlscan_is_create_routine(state->sub_idents)`
+ still true, the identifier `begin` increments `begin_depth`.
+* `src/fe_utils/psqlscan.l:1099` — the whole per-clause block is skipped once
+ `begin_depth > 0`, so no later clause can recover. An unquoted `end` would decrement, but
+ `END` is reserved in a `GRANT` clause and a double-quoted `"end"` is not tracked at all.
+* The `;` rule then does not terminate the statement, and psql keeps accumulating.
+
+**Rubric scores.**
+
+* `sql_mismatch = false`. 11.1 `<schema definition>` (found.txt:25906-25953) contains no
+ statement terminator, and the only place the standard terminates a *directly invoked*
+ statement with a semicolon is 22.1 (`<direct SQL statement> ::= <directly executable
+ statement> <semicolon>`, found.txt:50046-50047) — which 4.25 immediately disclaims:
+ "In direct invocation of SQL, the method of invoking <direct SQL statement>s ... are
+ implementation-defined" (found.txt:4840-4844). A client's script-splitting heuristic is
+ exactly that implementation-defined method. No standard citation is offered for this
+ finding.
+* `per_principles = true`. Master's own comment at `src/fe_utils/psqlscan.l:1093-1095`
+ states the property that is violated: "In CREATE SCHEMA, track identifiers from each
+ top-level CREATE schema element separately, so that BEGIN/END tracking is enabled only
+ within CREATE [OR REPLACE] {FUNCTION|PROCEDURE} clauses." It is not. `049b742`'s commit
+ message repeats the promise — "only counting BEGIN/END within those clauses". (Both are
+ 049b742's text, not d516974's; d516974's own hedge — "a bit shaky but should be okay with
+ the present set of valid subcommands" — no longer exists in master.) d516974's own
+ documentation lists `CREATE FUNCTION`, `CREATE PROCEDURE` and `GRANT` together as accepted
+ clauses (`doc/src/sgml/ref/create_schema.sgml:104-120`) under a Description paragraph
+ promising that "The subcommands are treated essentially the same as separate commands
+ issued after creating the schema" (`create_schema.sgml:61-65`; that paragraph is
+ 3450fd08/2003, so it is corroboration of master's standing principle rather than
+ documentation this commit wrote). Consequences are two of the rubric's automatic
+ triggers: a silently-wrong result (psql prints `CREATE SCHEMA` for a schema that is then
+ rolled back) and a misleading error (`VACUUM cannot run inside a transaction block` for a
+ script containing no transaction block).
+* `is_new = true`. `csx-old/src/fe_utils/psqlscan.l` and `csx-pre/src/fe_utils/psqlscan.l`
+ are byte-identical (`diff -q`), and neither records the token `schema` at all, so
+ `begin_depth` could never rise inside a `CREATE SCHEMA` before d516974. Behaviourally,
+ on `old` the same input is terminated at its semicolon
+ (`/home/nm/src/pg/csxrun/evidence/ADJ-C03-A-old.txt` CASE 1: the server reports
+ `ERROR: syntax error at or near "FUNCTION"` for the `CREATE SCHEMA` alone and the
+ following statement runs). The arming prefix — `CREATE [OR REPLACE] {FUNCTION|PROCEDURE}`
+ as a schema clause — is exactly what d516974 legalized.
+
+**This is NOT the case 049b742 deferred** — the discriminator matters, because a reviewer
+will otherwise reject it as already-conceded. 049b742's message defers
+`CREATE FUNCTION begin () ...`, "true all along with no field complaints". Measured on
+master (`/home/nm/src/pg/csxrun/evidence/XPREM-p6-new.txt`):
+
+| case | shape | result |
+|---|---|---|
+| K1 | `CREATE SCHEMA k1 CREATE FUNCTION begin () ...` | merged; `k1 schema exists: 0` |
+| K1b | the same routine **at top level** | merged too — the deferred family fails identically outside |
+| K2 | this finding: unremarkable `CREATE FUNCTION k2f ()`, `begin` in the following `GRANT` | merged; `k2 schema exists: 0` |
+| K2b | the identical routine **and** the identical `GRANT` at top level | **works**: `CREATE FUNCTION` / `GRANT` / `VACUUM` / `K2B_MARKER` |
+| K3 | pre-v19-legal `CREATE TABLE` clause + `GRANT ... TO begin` | **works**; `k3 schema exists: 1` |
+
+K1/K1b are the conceded family; the top-level heuristic is fooled and CREATE SCHEMA
+inherits it. K2/K2b are not: the top-level heuristic is *not* fooled by these tokens, and
+the leak is across the clause boundary — precisely what 049b742 promised to prevent. K3
+confirms 049b742's other claim, that nothing that worked before v19 was put at risk.
+
+**Honest narrowing.** The damage *class* is neither new nor CREATE SCHEMA-specific: a plain
+top-level `CREATE FUNCTION x1f() RETURNS int LANGUAGE sql SET search_path = begin AS 'select 1';`
+swallows a following `VACUUM` identically on master and on `old`
+(`XPREM-p3-new.txt`, `XPREM-p3-old.txt`). What is new is the leak across a clause boundary.
+The trigger also needs an object named with the unreserved keyword `begin` referenced
+unquoted in a `GRANT` clause after a routine clause.
+
+**Attribution: d516974** (introduced), narrowed by 049b742.
+
+**Other consequences verified on master, from the same merge:** pgbench (which links the same
+`psql_scan()`) reports one command instead of three in simple mode
+(`refute-C03-run3.txt` X4); `\i` runs the included file *before* the textually preceding
+`CREATE SCHEMA` (`refute-C03-run3.txt` X3, `H13-psqlmodes-new-1.txt` P5); the poisoning
+identifier can arrive via `:var` interpolation (`H13-psqlmodes-new-1.txt` P8), so a script
+whose text never contains the word `begin` can be poisoned from outside it — with
+`psql -X -v r=begin -f`, a file containing only `GRANT USAGE ON SCHEMA v1 TO :r;` in the
+`GRANT` clause merges and the schema is rolled back, while `-v r=public` splits correctly
+(`FACTCHECK-D1-var-new.txt`).
+
+**Test on this branch: yes.** `src/bin/psql/t/001_basic.pl`, subtests 135-137 (of 140),
+added at lines 580-618 with a passing control at subtests 138-140. Verbatim failure
+(`/home/nm/src/pg/csxrun/evidence/FINAL-tap.txt`):
+
+```
+not ok 135 - CREATE SCHEMA ends at its semicolon: exit code 0
+# got: '3'
+not ok 136 - CREATE SCHEMA ends at its semicolon: no stderr
+# got: 'psql:<stdin>:6: ERROR: VACUUM cannot run inside a transaction block'
+not ok 137 - CREATE SCHEMA ends at its semicolon: matches
+# ''
+```
+
+The expected values encode correct behaviour, so the test fails on master by design.
+
+---
+
+### D2 — a `SET role` / `SET session_authorization` clause on a routine element is refused under `AUTHORIZATION`, naming a security-definer function that does not exist
+
+**Severity: low. Reachability is new in d516974; the rule violation is reachable on `pre` by
+a harder route (see Attribution).**
+
+**One-line statement.** Inside `CREATE SCHEMA ... AUTHORIZATION <role other than the current
+user>`, every `SET`/`RESET` clause on a `CREATE [OR REPLACE] FUNCTION`/`PROCEDURE` element
+that names `role` or `session_authorization` is rejected — including when the invoker is a
+superuser and the `AUTHORIZATION` role is a superuser — with a message that is false.
+
+**Minimal reproduction** (build `new`, as the bootstrap superuser):
+
+```sql
+CREATE ROLE regress_plan_super SUPERUSER;
+CREATE SCHEMA sa1 AUTHORIZATION regress_plan_super
+ CREATE FUNCTION f1() RETURNS int LANGUAGE sql SET role = 'regress_plan_super' AS 'select 1';
+```
+
+**Literal observed output** (`/home/nm/src/pg/csxrun/evidence/PLAN-o1o2-new.txt`):
+
+```
+CREATE SCHEMA sa1 AUTHORIZATION regress_plan_super
+ CREATE FUNCTION f1() RETURNS int LANGUAGE sql SET role = 'regress_plan_super' AS 'select 1';
+psql:/home/nm/src/pg/csxrun/probe/plan/o1o2.sql:6: ERROR: cannot set parameter "role" within security-definer function
+```
+
+Controls in the same transcript: the identical `CREATE FUNCTION` **at top level** succeeds
+(`O1d`: `CREATE FUNCTION`); the identical element inside `CREATE SCHEMA` **without**
+`AUTHORIZATION` succeeds (`O1c`: `CREATE SCHEMA`); `AUTHORIZATION CURRENT_USER`, where
+`saved_uid == owner_uid` so `SetUserIdAndSecContext` is skipped, succeeds (`O1f`); a plain
+non-role `SET` clause under `AUTHORIZATION` succeeds (`O1g`). Every spelling that names
+either GUC fails — twelve were enumerated and all twelve error
+(`/home/nm/src/pg/csxrun/evidence/FACTCHECK-D2-spellings-new.txt`): `SET role =`,
+`SET role TO`, `SET role TO DEFAULT`, `SET role FROM CURRENT`, `RESET role`, the four
+`session_authorization` analogues, and `SET SESSION AUTHORIZATION <literal>`,
+`SET SESSION AUTHORIZATION DEFAULT`, `RESET SESSION AUTHORIZATION`. `RESET ALL` alone slips
+through (`/home/nm/src/pg/csxrun/evidence/h02-a-new.txt` A1, verbatim):
+
+```
+CREATE SCHEMA a1c AUTHORIZATION regress_bob CREATE FUNCTION f() RETURNS int LANGUAGE sql AS 'select 1' RESET role;
+psql:/home/nm/src/pg/csxrun/probe/h02-a.sql:15: ERROR: cannot set parameter "role" within security-definer function
+CREATE SCHEMA a1d AUTHORIZATION regress_bob CREATE FUNCTION f() RETURNS int LANGUAGE sql AS 'select 1' RESET session_authorization;
+psql:/home/nm/src/pg/csxrun/probe/h02-a.sql:16: ERROR: cannot set parameter "session_authorization" within security-definer function
+CREATE SCHEMA a1e AUTHORIZATION regress_bob CREATE FUNCTION f() RETURNS int LANGUAGE sql AS 'select 1' RESET ALL;
+CREATE SCHEMA
+```
+
+Note `RESET role`: nothing is being *set* at all, yet the message says "cannot set
+parameter".
+
+**What a correct implementation produces, and why.** The documented equivalent —
+`SET ROLE r; CREATE FUNCTION r_schema.f() ... SET role = 'r';` — succeeds and stores
+`proconfig = {role=r}`, and so does the same `CREATE SCHEMA` when `AUTHORIZATION` names the
+current role. Verbatim oracle from master
+(`/home/nm/src/pg/csxrun/evidence/PLAN-oracle-new.txt`, `AUTHORIZATION CURRENT_ROLE` under
+`SET ROLE`):
+
+```
+ proname | nspname | owner | proconfig
+------------+------------------------+----------------------------+----------------------------------------------------
+ cs_setrole | regress_schema_setrole | regress_create_schema_role | {role=regress_create_schema_role}
+ cs_setsess | regress_schema_setrole | regress_create_schema_role | {session_authorization=regress_create_schema_role}
+(2 rows)
+```
+
+**Mechanism** (against 6885b84).
+
+* `src/backend/commands/schemacmds.c:149-150` —
+ `SetUserIdAndSecContext(owner_uid, save_sec_context | SECURITY_LOCAL_USERID_CHANGE)` for
+ the whole element body, so `InLocalUserIdChange()` is true (and
+ `InSecurityRestrictedOperation()` is not).
+* `src/backend/utils/misc/guc.c:3541-3553` — the `GUC_NOT_WHILE_SEC_REST` block tests
+ `InLocalUserIdChange()` *before* consulting `changeVal`, so a validation-only call is
+ rejected. The comment at `guc.c:3546-3548` itself calls the phrasing "historical, but
+ it's the most common case".
+* Two call paths reach it and a fix must handle both — established by a
+ `backtrace_functions` trace during the verify pass: first
+ `CreateFunction -> GUCArrayAdd -> validate_option_array_item -> set_config_option(..., changeVal = false, ...)`,
+ which is what fails today; then `ProcedureCreate -> ProcessGUCArray(..., GUC_ACTION_SAVE)`
+ with `changeVal = true`, which applies proconfig around the language validator (gated on
+ `check_function_bodies`, `pg_proc.c:742-755`). The verify pass reports that gating the
+ guard on `changeVal` alone still fails, because the second site then hits it; what is
+ re-verified here is that `check_function_bodies = off` does **not** avoid the error,
+ because the first site still fires
+ (`/home/nm/src/pg/csxrun/evidence/VERIFY-o1-mechanism-new.txt`). A fix must also stop
+ short of actually applying the value: a throwaway patch that
+ simply dropped the guard PANICked the server on
+ `Assert(SecurityRestrictionContext == 0)` — `miscinit.c:491`, in `SetOuterUserId()`,
+ which the `role` GUC's assign path reaches via `SetCurrentRoleId()` (`miscinit.c:981`);
+ `SetSessionUserId()` carries the same assertion at `miscinit.c:525`. (That throwaway
+ tree was deleted after the verify pass, so this one item is reported, not re-run, by the
+ fact-check; the two assertions and both call paths are verified in master's source.)
+* Only `role` and `session_authorization` carry `GUC_NOT_WHILE_SEC_REST`;
+ `functioncmds.c:683-684` short-circuits `VAR_RESET_ALL`, which is why `RESET ALL` escapes.
+
+**Rubric scores.**
+
+* `sql_mismatch = false`. `proconfig` SET clauses have no counterpart in ISO/IEC 9075, and
+ 11.1's Access Rules make the privilege environment implementation-defined. No citation is
+ offered.
+* `per_principles = true`, on two independent grounds. (a) The rubric's automatic trigger:
+ the message names a security-definer function, and there is none anywhere — at top level a
+ bad value instead gives the informative `NOTICE: role "..." does not exist` +
+ `ERROR: role "..." does not exist` (`h02-e-new.txt` E5), so the false message pre-empts the
+ accurate one. (b) d516974 added `CREATE FUNCTION` and `CREATE PROCEDURE` to the
+ accepted-clause list it wrote at `create_schema.sgml:104-120`, sitting under a Description
+ paragraph whose only stated exception is ownership.
+* `is_new`: **true for this spelling, false for the rule.** On `old` and `pre` the statement
+ is `ERROR: syntax error at or near "FUNCTION"`. But on `pre`, using only pre-d516974-legal
+ elements, the identical false message already appears in the identical environment
+ (`/home/nm/src/pg/csxrun/evidence/adjC12-a3-pre.txt`, verbatim):
+
+ ```
+ CREATE SCHEMA sq AUTHORIZATION regress_bob
+ CREATE TABLE parent (a int) PARTITION BY RANGE (a)
+ CREATE TABLE child PARTITION OF parent FOR VALUES FROM (public.fx(1)) TO (public.fx(10));
+ psql:/home/nm/src/pg/csxrun/probe/adjC12/a3.sql:9: ERROR: cannot set parameter "role" within security-definer function
+ ```
+
+ where `public.fx` merely carries `proconfig = {role=regress_bob}`; the documented
+ separate-command form in the same transcript succeeds. Because `per_principles` is true,
+ the verdict does not depend on `is_new`.
+
+**Attribution: shared.** d516974 owns the reachability (one statement, no setup, from a
+clause it added and documented) and the specific *validation-only* instance; the guc.c and
+schemacmds.c code on the failing path is untouched by d516974 —
+`git show --stat d516974` modifies no file under `src/backend/commands/` or
+`src/backend/utils/`. A reviewer who keys attribution strictly on the rule violation should
+read this section together with §6's rejected candidate C12 and file it as pre-existing,
+newly exposed. Either way the fix belongs in `guc.c` (consult `changeVal`, fix the wording)
+or `schemacmds.c` (push a GUC nest level so the userid change need not be "local"), never in
+anything d516974 wrote.
+
+**Also reachable from a `CREATE EXTENSION` script** — superuser-installed and non-superuser
+trusted alike (`h02-f-new.txt` F2/F3/F7), where it blocks the natural way to write an
+extension that ships a routine with a `SET role` clause into an `AUTHORIZATION`-owned schema.
+
+**Test on this branch: yes.** `src/test/regress/{sql,expected}/create_schema.{sql,out}`,
+block at `sql/create_schema.sql:188-211`; failure hunk `@@ -347,17 +347,17 @@` of
+`testrun/regress/regress/regression.diffs`
+(`/home/nm/src/pg/csxrun/evidence/FINAL-regressdiffs.txt`):
+
+```
++ERROR: cannot set parameter "role" within security-definer function
+ SELECT p.proname, pg_get_userbyid(p.proowner) AS owner, p.proconfig
+ FROM pg_proc p JOIN pg_namespace n ON n.oid = p.pronamespace
+ WHERE n.nspname = 'regress_schema_setrole' ORDER BY 1;
+- proname | owner | proconfig
+-------------+----------------------------+----------------------------------------------------
+- cs_setrole | regress_create_schema_role | {role=regress_create_schema_role}
+- cs_setsess | regress_create_schema_role | {session_authorization=regress_create_schema_role}
+-(2 rows)
++ proname | owner | proconfig
++---------+-------+-----------
++(0 rows)
+
+ SET client_min_messages = warning;
+ DROP SCHEMA regress_schema_setrole CASCADE;
++ERROR: schema "regress_schema_setrole" does not exist
+```
+
+---
+
+### D3 — PL/pgSQL cannot contain a `CREATE SCHEMA` whose element is a `BEGIN ATOMIC` routine
+
+**Severity: low. New in d516974.**
+
+**One-line statement.** `pl_gram.y`'s copy of psql's statement-boundary heuristic recognizes
+`BEGIN`/`END` only after a leading `CREATE [OR REPLACE] {FUNCTION|PROCEDURE}`, never after
+`CREATE SCHEMA`, so it cuts the statement at the first semicolon inside the routine body —
+making a construct that `create_schema.sgml` lists as accepted unwritable in PL/pgSQL.
+
+**Minimal reproduction** (build `new`):
+
+```sql
+DO $$ BEGIN CREATE SCHEMA a_s CREATE FUNCTION a_f() RETURNS int BEGIN ATOMIC SELECT 1; END; END $$;
+```
+
+**Literal observed output** (`/home/nm/src/pg/csxrun/evidence/adjC14-new-1.txt`, case A):
+
+```
+DO $$ BEGIN CREATE SCHEMA a_s CREATE FUNCTION a_f() RETURNS int BEGIN ATOMIC SELECT 1; END; END $$;
+psql:/home/nm/src/pg/csxrun/probe/adjC14/adj.sql:2: ERROR: syntax error at end of input
+LINE 1: ...E FUNCTION a_f() RETURNS int BEGIN ATOMIC SELECT 1; END; END...
+ ^
+```
+
+Controls in the same transcript: the same routine in PL/pgSQL but **not** inside
+`CREATE SCHEMA` returns `DO` (case B); the same `CREATE SCHEMA` submitted directly returns
+`CREATE SCHEMA` (case C); the same statement via plpgsql `EXECUTE` returns `DO` (case D); an
+`EXCEPTION` handler does not catch it, because it is a compile-time failure (case G); and a
+named plpgsql function containing it fails at `CREATE FUNCTION` time (case H). The trigger
+is exactly one or more statements in the `ATOMIC` body — an empty body works (case F).
+
+**What a correct implementation produces, and why.** The byte-identical `CREATE SCHEMA`
+submitted at top level on master succeeds
+(`/home/nm/src/pg/csxrun/evidence/PLAN-oracle4-new.txt`):
+
+```
+create schema misc_schema
+ create function test3() returns int
+ begin atomic
+ select 3 + 3;
+ end;
+CREATE SCHEMA
+CREATE OR REPLACE FUNCTION misc_schema.test3()
+ RETURNS integer
+ LANGUAGE sql
+BEGIN ATOMIC
+ SELECT (3 + 3);
+END
+```
+
+so the correct output is a real oracle, not a guess.
+
+**Mechanism** (against 6885b84). `src/pl/plpgsql/src/pl_gram.y:3032` `make_execsql_stmt()`
+keeps `char tokens[4]` (`:3049`) and updates it only while
+`tokens[0] == 'c' && token_count < sizeof(tokens)` (`:3104-3105`). For
+`CREATE SCHEMA s CREATE FUNCTION ...` the trace is: `CREATE` → `tokens[0]='c'`;
+`SCHEMA` → nothing (`schema` is a PL/pgSQL *unreserved keyword*,
+`src/pl/plpgsql/src/pl_unreserved_kwlist.h:101`, so `plpgsql_yylex` returns `K_SCHEMA` and
+none of the `tok == T_WORD && strcmp(...)` tests at `:3107-3116` can fire); then the name;
+then the second `CREATE`; then the block is skipped forever. `in_routine_definition`
+(`:3047`) stays false, `begin_depth` stays 0, and the `;` after `SELECT 1` breaks the scan at
+`:3136-3137`. The comment at `:3065-3066` still says "We follow psql's lead in not
+recognizing BEGIN/END except after CREATE [OR REPLACE] {FUNCTION|PROCEDURE}" — a statement of
+intent that this very series made false, since psql's lead moved in d516974 + 049b742 and
+`pl_gram.y` was not updated by any commit in the series.
+
+Structurally this can never be *silent*: `tokens[1]` is the token after `CREATE`, which for
+`CREATE SCHEMA` is always `SCHEMA`, so `in_routine_definition` can never be spuriously true
+and truncation always cuts a routine body before its `END` (verified with a leading
+`CREATE TABLE` clause that would leave a valid prefix — `refC14-r2-new.txt` R2-C still
+errors, nothing created).
+
+**Rubric scores.**
+
+* `sql_mismatch = false`, deliberately. `grep -ci 'plpgsql\|PL/pgSQL' found.txt` = 0;
+ 11.1's Syntax Rules constrain what contained elements may name, and nothing in the standard
+ speaks to how a vendor's procedural language finds the end of an embedded statement. The
+ server accepts the SQL — cases C and D prove it.
+* `per_principles = true`. d516974's own documentation lists `CREATE FUNCTION` and
+ `CREATE PROCEDURE` as accepted clauses (`create_schema.sgml:104-120`), and
+ `doc/src/sgml/plpgsql.sgml` still says without qualification that "In general, any SQL
+ command that does not return rows can be executed within a PL/pgSQL function just by
+ writing the command." Case B proves the identical routine written as a separate command in
+ the same `DO` block works, so the documented equivalence fails only for the
+ in-`CREATE SCHEMA` form. (The "misleading error message" argument is weaker than it looks
+ and is *not* relied on: in the realistic multi-line form the display is
+ `LINE 7: select 3 + 3;` with the caret at end of line and nothing after it —
+ `refC14-r1-new.txt` R1-F, `XPREM-p5-new.txt` — and running the truncated fragment alone
+ produces the identical message legitimately, `refC14-r2-new.txt` R2-A.)
+* `is_new = true`, by reachability rather than by file ownership. On `old` and `pre` the
+ same `DO` block fails with `ERROR: syntax error at or near "FUNCTION"`
+ (`adjC14-old-1.txt`, `adjC14-pre-1.txt`, byte-identical) — the backend grammar rejects the
+ element outright, so no documented-legal element was being refused and there was no rule to
+ violate. Every pre-d516974-legal element shape with a depth-0 hazard passes on `old`:
+ semicolon inside a string literal, `CASE ... END` in a view, four chained elements
+ including `GRANT`, and 049b742's `CREATE VIEW begin` case — all return `DO`, `q1_ok..q4_ok = 1`
+ (`/home/nm/src/pg/csxrun/evidence/XPREM-p2-old.txt`).
+
+**Attribution: d516974.** The heuristic in `pl_gram.y` is `57b440ec` (2024) and was not
+touched by the series; what d516974 supplies is the documented-legal construct that the
+heuristic cannot parse. Note for a fixer: PL/pgSQL is *immune* to the hazard 049b742 fixed
+(`DO $$ BEGIN CREATE SCHEMA m5 CREATE VIEW begin AS SELECT 1; END $$;` returns `DO`,
+`refC14-r4-new.txt` R4-5) precisely because `in_routine_definition` is never set — so a naive
+"count BEGIN/END throughout CREATE SCHEMA" port would import that regression. `pl_gram.y`'s
+other known semicolon hazard, `CREATE RULE` with multiple actions, is already covered by its
+`paren_depth` counter and works in a `DO` block on all three builds
+(`refC14-r1-new.txt` R1-D, `refC14-r3-oldpre.txt` R3-B), so this is the sole outstanding gap
+in that heuristic.
+
+**Test on this branch: yes.** `src/pl/plpgsql/src/{sql,expected}/plpgsql_misc.{sql,out}`,
+block at `sql/plpgsql_misc.sql:24-53`, sitting beside the `57b440e` test it mirrors. Single
+failure hunk `@@ -55,15 +55,13 @@` (`/home/nm/src/pg/csxrun/evidence/FINAL-regressdiffs.txt`):
+
+```
++ERROR: syntax error at end of input
++LINE 7: select 3 + 3;
++ ^
+ \sf misc_schema.test3
+-CREATE OR REPLACE FUNCTION misc_schema.test3()
+- RETURNS integer
+- LANGUAGE sql
+-BEGIN ATOMIC
+- SELECT (3 + 3);
+-END
++ERROR: function "misc_schema.test3" does not exist
+ drop schema misc_schema cascade;
+-NOTICE: drop cascades to function misc_schema.test3()
++ERROR: schema "misc_schema" does not exist
+```
+
+---
+
+### D4 — psql offers `COLLATION` and `TYPE` as `CREATE SCHEMA` clauses but does not complete them
+
+**Severity: cosmetic. This is the weakest item in this report; the counterarguments are
+stated in full below.**
+
+**One-line statement.** d516974 added `COLLATION` and `TYPE` to the clause keywords psql
+offers after `CREATE SCHEMA ... CREATE`, and in the same diff converted two other clause
+families (`CREATE DOMAIN`, `CREATE TEXT SEARCH`) from anchored `Matches()` to `TailMatches()`
+"so use TailMatches" — but left `CREATE COLLATION`'s and `CREATE TYPE`'s own rules anchored,
+so those two complete at top level and not inside `CREATE SCHEMA`.
+
+**Minimal reproduction** — interactive psql with readline; the audit used
+`src/bin/psql/t/010_tab_completion.pl`'s own pty harness. Type
+`CREATE SCHEMA s CREATE COLLATION c FR<TAB>` and `CREATE SCHEMA s CREATE TYPE t AS EN<TAB>`.
+
+**Literal observed output** (`/home/nm/src/pg/csxrun/evidence/FINAL-tap.txt`):
+
+```
+not ok 110 - complete CREATE COLLATION inside CREATE SCHEMA
+# Actual output was "CREATE SCHEMA s CREATE COLLATION c FR\r\rpostgres=# \r\rpostgres=# \\r\r\nQuery buffer reset (cleared).\r\npostgres=# "
+# Did not match "(?^:CREATE COLLATION c FROM )"
+not ok 111 - complete CREATE TYPE inside CREATE SCHEMA
+# Actual output was "CREATE SCHEMA s CREATE TYPE t AS EN\r\rpostgres=# \r\rpostgres=# \\r\r\nQuery buffer reset (cleared).\r\npostgres=# "
+# Did not match "(?^:CREATE TYPE t AS ENUM )"
+```
+
+Nothing is offered: the tab is inert.
+
+**What a correct implementation produces, and why.** The byte-identical tails complete at
+top level, and the two families d516974 *did* convert complete inside `CREATE SCHEMA`. All
+four are passing subtests in the same run: `ok 102 - complete CREATE DOMAIN inside CREATE
+SCHEMA`, `ok 104 - complete CREATE TEXT SEARCH inside CREATE SCHEMA`,
+`ok 112 - complete CREATE COLLATION at top level`,
+`ok 114 - complete CREATE TYPE at top level`; plus
+`ok 106`/`ok 108`, which show psql offering `COLLATION` and `TYPE` as clauses in the first
+place (`/home/nm/src/pg/csxrun/evidence/CLIENT-O3-tap-new.txt`).
+
+**Mechanism** (against 6885b84).
+
+* `src/bin/psql/tab-complete.in.c:2199-2207` — the clause list, rewritten by d516974, now
+ offers `"AGGREGATE", "COLLATION", "DOMAIN", "FUNCTION", "INDEX", "OPERATOR", "PROCEDURE",
+ "SEQUENCE", "TABLE", "TEXT SEARCH ...", "TRIGGER", "TYPE", "VIEW"` (plus `"UNIQUE"` and
+ `"UNLOGGED"`, for `INDEX` and `TABLE`/`SEQUENCE` respectively).
+* `tab-complete.in.c:3521` — `/* CREATE DOMAIN --- is allowed inside CREATE SCHEMA, so use
+ TailMatches */`, and `:3893` the same for `CREATE TEXT SEARCH`: both converted by d516974.
+* `tab-complete.in.c:3493`, `:3495` — `Matches("CREATE", "COLLATION", MatchAny)` and
+ `Matches("CREATE", "COLLATION", MatchAny, "FROM")`: still anchored.
+* `tab-complete.in.c:4163`, `:4165`, `:4174` — `Matches("CREATE", "TYPE", ...)`: still
+ anchored. `Matches()` requires `previous_words_count == narg`, which cannot hold once
+ `CREATE SCHEMA s` precedes.
+
+**Correction to the orchestrator's original statement of this finding.** It named
+`CREATE FUNCTION`, `CREATE PROCEDURE`, `CREATE AGGREGATE` and `CREATE OPERATOR` as also
+affected. They are not: an exhaustive grep of master's `tab-complete.in.c` for
+`(Matches|TailMatches|HeadMatches)("CREATE", "FUNCTION|PROCEDURE|AGGREGATE|OPERATOR"` returns
+nothing — psql has no completion rules for those four object types anywhere, inside or
+outside `CREATE SCHEMA`. The defect is exactly `CREATE COLLATION` (3 rules) and
+`CREATE TYPE` (6 rules). Of the nine, four (`:3497`, `:4167`, `:4176`, `:4190`) cannot be
+rewritten as `TailMatches` at all — they anchor the head of an open parenthesised option
+list, and psql's matcher vocabulary has no tail-anchored-head form. The actionable omission
+is five one-word edits (`:3493`, `:3495`, `:4163`, `:4165`, `:4174`).
+
+**Rubric scores.**
+
+* `sql_mismatch = false` — and for the right reason. Collations and user-defined types *are*
+ `<schema element>`s in the standard (found.txt:25931-25953), but ISO/IEC 9075 has no
+ requirements about a client's interactive completion. On the server axis d516974 made
+ PostgreSQL strictly more conformant.
+* `per_principles = true`, narrowly: the commit wrote the rule into the tree in its own diff
+ ("--- is allowed inside CREATE SCHEMA, so use TailMatches"), added `COLLATION` and `TYPE` to
+ the list of clauses it advertises, and did not apply the rule to them. That is an
+ internal-coherence gap in one commit's own diff.
+* `is_new = true` for the two families, but see the counterarguments.
+
+**Counterarguments a reviewer should weigh — all of them verified, none of them refuted.**
+
+1. The invariant "everything psql offers as a clause completes inside `CREATE SCHEMA`" was
+ *never* universal. `CREATE TRIGGER` — one of the five clause keywords psql offered before
+ d516974 — goes dead inside `CREATE SCHEMA` from `ON <table>` onward, on `old`, `pre` and
+ master alike. Measured on `old` (`/home/nm/src/pg/csxrun/evidence/refute-C18-old-1.txt`):
+ `[CREATE SCHEMA s CREATE TRIGGER trg AFTER INSERT ON tab1 ]` + TAB TAB offers nothing,
+ while the same tail at top level offers
+ `DEFERRABLE FOR NOT DEFERRABLE WHEN ( EXECUTE FUNCTION INITIALLY REFERENCING`.
+2. Un-anchoring is a documented, complaint-driven backlog. `tab-complete.in.c:20-22` says
+ the file "does not always give you all the syntactically legal completions"; commit
+ `9b181b0`, where head-anchoring became the default and where the very "so use TailMatches"
+ comment was written, says "we can put those back when we get complaints"; and `ef0938f`
+ (2024) *added* head-anchored `CREATE TRIGGER` rules under that comment.
+3. The mechanism is generic, not CREATE SCHEMA-specific: `[BEGIN; CREATE TYPE t ]` + TAB is
+ equally dead, and `[BEGIN; CREATE COLLATION c FROM ]` + TAB offers
+ `information_schema. public. mytab123 tab1` — on `new`, `old` and `pre` alike
+ (`/home/nm/src/pg/csxrun/evidence/refute-C18-new-1.txt` lines 120-186). The
+ `COLLATION ... FROM` case offering non-collation names is therefore a pre-existing psql
+ behaviour and is **not** charged to d516974.
+
+**Attribution: d516974**, for the two families it advertised and did not convert. Cosmetic:
+no crash, no wrong catalog contents, no silently-wrong result; a user who accepts a wrong
+suggestion gets an immediate, accurate server error.
+
+**Test on this branch: yes.** `src/bin/psql/t/010_tab_completion.pl`, subtests 110-111 (of
+115), body at lines 486-556, with six passing controls around them (102, 104, 106, 108, 112,
+114). A new helper `check_completion_nofail()` at lines 129-150 is *required*, not cosmetic:
+the file's own `check_completion()` waits `PG_TEST_TIMEOUT_DEFAULT` and then **dies**, which
+would abort the file after two subtests
+(`/home/nm/src/pg/csxrun/evidence/PLAN-o3-die-new.txt`).
+
+---
+
+## 3. Complete failure set produced by this branch's tests
+
+The four tests above make **seven** meson tests fail, not four. Three of the seven are
+collateral: `pg_upgrade/002_pg_upgrade`, `recovery/027_stream_regress` and
+`test_plan_advice/001_replan_regress` each run the core `parallel_schedule`, so each fails
+only on the shared `create_schema` test with a `regression.diffs` whose body is byte-identical
+to `regress/regress`'s. That is inherent to any regress-file test encoding correct behaviour.
+
+From `/home/nm/src/pg/csxrun/evidence/FINAL-suiterun.txt`:
+
+```
+1/9 postgresql:psql / psql/020_cancel OK 1.32s 2 subtests passed
+2/9 postgresql:psql / psql/030_pager OK 1.54s 5 subtests passed
+3/9 postgresql:psql / psql/010_tab_completion ERROR 1.64s exit status 2
+4/9 postgresql:plpgsql / plpgsql/regress ERROR 3.98s exit status 1
+5/9 postgresql:psql / psql/001_basic ERROR 8.11s exit status 3
+6/9 postgresql:regress / regress/regress ERROR 87.69s exit status 1
+7/9 postgresql:test_plan_advice / test_plan_advice/001_replan_regress ERROR 90.62s exit status 1
+8/9 postgresql:recovery / recovery/027_stream_regress ERROR 95.70s exit status 1
+9/9 postgresql:pg_upgrade / pg_upgrade/002_pg_upgrade ERROR 99.04s exit status 1
+Ok: 2
+Expected Fail: 0
+Fail: 7
+```
+
+| meson test | failing unit | finding |
+|---|---|---|
+| `regress/regress` | `create_schema`, 1 of 243 | D2, and S1 below |
+| `plpgsql/regress` | `plpgsql_misc`, 1 of 13 | D3 |
+| `psql/001_basic` | subtests 135-137 of 140 | D1 |
+| `psql/010_tab_completion` | subtests 110-111 of 115 | D4 |
+| `pg_upgrade/002_pg_upgrade` | subtest 5 "regression tests in old instance" | collateral |
+| `recovery/027_stream_regress` | subtest 2 "regression tests pass" | collateral |
+| `test_plan_advice/001_replan_regress` | subtest 1 "regression tests pass" | collateral |
+
+`pg_upgrade` subtest 20 ("old and new dumps match after pg_upgrade") and `recovery` subtests 7
+and 10 still pass, proving the added blocks leave no catalog residue. With the additions
+stashed, all seven are green. Four of the five shipped tests were shown to flip to passing
+under a throwaway fix (D1 by resetting sub-clause state on `grant`; D3 by porting psqlscan.l's
+per-clause tracking to `pl_gram.y`; D4 by two `Matches` → `TailMatches` edits; D2 by a guc.c
+flag, which removes the D2 hunk and leaves only S1's two); S1 was not fix-checked because a
+fix is a redesign.
+
+---
+
+## 4. Findings that belong to the sibling commit a9c350d, not to d516974
+
+These are presented as **context**. Each is a real, user-visible defect in master, and one
+of them (S2) is the most severe thing this audit found anywhere — but each reproduces on
+`old` (= `d516974^`) with a pre-d516974-legal element, so d516974 did not cause it. Nothing
+was found attributable to `404db8f`.
+
+The common cause: `a9c350d` deleted `setSchemaName()`, which used to write the new schema's
+name into each element's `RangeVar->schemaname`
+(`/home/nm/src/pg/csx-pre/src/backend/parser/parse_utilcmd.c:4521-4535`, called at
+`:4450/4459/4472/4485/4494`), and replaced it with `checkSchemaNameRV()`, which only
+*verifies* an explicitly written name and leaves NULL as NULL. From then on the new schema
+reaches its own elements solely through a prepended `search_path` entry
+(`schemacmds.c:165-178`).
+
+### S1 — `CREATE SCHEMA "$user"` receives none of its own elements
+
+`quote_identifier("$user")` yields `"$user"`; `SplitIdentifierString` strips the quotes; and
+`preprocessNamespacePath` (`src/backend/catalog/namespace.c:4211`) compares
+`strcmp(curname, "$user") == 0` and substitutes the schema named after `GetUserId()`. The
+new schema therefore never enters the path it was supposed to head.
+
+```sql
+CREATE SCHEMA "$user" CREATE TABLE t (a int);
+SELECT relnamespace::regnamespace AS t_landed_in FROM pg_class WHERE relname = 't';
+```
+
+Observed on `new` (`/home/nm/src/pg/csxrun/evidence/PLAN-oracle-new.txt`,
+`replC08-clean-new.txt` Y1): `CREATE SCHEMA`, then `t_landed_in` = `public`, and the schema
+that was named is left empty. Variants, all verified: with a schema named after the current
+role present, the elements land *there*; with `search_path = pg_temp, public` a plain,
+non-`TEMP` `CREATE TABLE` element comes out `relpersistence = 't'` in `pg_temp_0` and vanishes
+at session end (`refC08-r7-new.txt`); with a genuinely empty path the statement dies
+`ERROR: no schema has been selected to create in` for the schema it just created itself;
+and a `CREATE OR REPLACE VIEW`/`FUNCTION` element silently overwrites the object of that name
+in `public` (`refC08-r3-new.txt` §E). Self-contradiction: the *qualified* spelling
+`CREATE SCHEMA "$user" CREATE TABLE public.tq (a int)` is rejected with
+`ERROR: CREATE specifies a schema (public) different from the one being created ($user)`
+while the unqualified spelling silently does exactly what the guard forbids
+(`replC08-x5-new.txt` U1/U2).
+
+`sql_mismatch = true`: ISO/IEC 9075-2 5.4 SR 4)b) (found.txt:8701-8702) reads verbatim
+"If the <local or schema qualified name> is contained in a <schema definition>, then the
+<schema name> that is specified or implicit in the <schema definition> is implicit." With
+`<table name> ::= <local or schema qualified name>` (found.txt:8509) and 11.1 GR 1)e)
+(found.txt:26007), `CREATE SCHEMA "$user" CREATE TABLE t (a int)` shall create `"$user".t`.
+Three scope limits: SR 4 covers only names *without* a qualifier; it reaches only object
+kinds the standard has, so it must **not** be cited for the `CREATE INDEX`, aggregate,
+operator or text-search instances; and 11.1 SR 6/SR 8 do **not** license the prepended path
+as the *placement* mechanism, because SR 8 (found.txt:25984-25986) scopes the SQL-path to
+"unqualified <routine name>s that are immediately contained in <routine invocation>s", not to
+where an element's own name is created.
+
+`is_new = false`, decisively: `old` and `new` agree on every variant expressible in
+pre-d516974 syntax, and `pre` is correct on all of them
+(`adjC08-new.txt` / `adjC08-old.txt` / `adjC08-pre.txt`; `refC08-r3-{new,old,pre}.txt`).
+`CREATE TABLE` alone reproduces the whole thing on `old`. Severity low, and the trigger is
+narrower than it first appears: if a role literally named `$user` is the effective user, the
+macro resolves to the new schema and everything is correct, so the `AUTHORIZATION`-derived
+route — the only way such a schema plausibly arises without deliberate intent — is safe
+(`refC08-r6-new.txt` §V/§V2/§V3). Exactly two schema names are affected (`$user` and
+`pg_temp`); `a,b`, `a"b`, `PUBLIC`, `select`, `$USER`, `$user2`, ` $user`, `üser`, embedded
+newlines and 63-byte names all land correctly (`refC08-r4-new.txt`).
+
+**A test for S1 is on this branch** because it is cheap and unambiguous, clearly labelled as
+a9c350d's: `src/test/regress/{sql,expected}/create_schema.{sql,out}` block at
+`sql/create_schema.sql:213-238`, hunks `@@ -376,14 +376,14 @@` and `@@ -392,13 +392,12 @@`.
+Both halves run inside `BEGIN ... ROLLBACK` so that neither the buggy nor the fixed behaviour
+leaves objects in another schema. The oracles are real: the first half's expected output is
+master's own behaviour run as a role literally named `$user`; the second half's is build
+`pre` (`/home/nm/src/pg/csxrun/evidence/PLAN-oracle3-pre.txt`).
+
+### S2 — the session temp schema outranks the new schema for unqualified names
+
+`finalNamespacePath` (`namespace.c:4291-4305`) sets `*firstNS` (the creation namespace) from
+the explicit path, then `lcons()`es `myTempNamespace` and `pg_catalog` onto the *front* of
+the lookup list. So `CREATE SCHEMA k CREATE TABLE s (y int) CREATE INDEX ON s (y)` indexes a
+pre-existing *temp* table named `s` rather than the table the first element just created.
+This is the most severe item in the audit: it reaches a genuine confused deputy, verified on
+`old` (`/home/nm/src/pg/csxrun/evidence/adjC07-G-old.txt`), where an unprivileged caller with
+a colliding temp table makes a superuser-owned SECURITY DEFINER `CREATE SCHEMA` attach a
+trigger to their own temp table. It is nevertheless **not a d516974 finding and arguably not
+a defect at all**: every symptom reproduces with `SET search_path` and no `CREATE SCHEMA`
+anywhere, byte-identically on `new`, `old` and `pre`
+(`ref-C07-ROUT-new.txt`, `ref-C07-RSD-{new,pre}.txt`), and both halves are documented.
+`doc/src/sgml/config.sgml:10349-10353`: "If it is not listed in the path then it is searched
+first (even before `pg_catalog`). However, the temporary schema is only searched for relation
+(table, view, sequence, etc.) and data type names. It is never searched for function or
+operator names." — one paragraph that predicts every symptom, including that functions and
+operators are immune. And `doc/src/sgml/ref/create_function.sgml:800` prescribes "write
+`pg_temp` ... as the last entry in `search_path`", which defeats the escalation entirely.
+No test.
+
+### S3 — an unqualified relation reference in an index or trigger element binds through `search_path`
+
+`CREATE SCHEMA s CREATE INDEX ON public_tab (a)` puts the index in `public`. Verified on
+master (`/home/nm/src/pg/csxrun/evidence/PLAN-oracle-new.txt`): `CREATE SCHEMA`, then
+
+```
+ relname | nspname
+----------------+---------
+ cs_outside_idx | public
+ cs_outside_tab | public
+```
+
+Two limits on the standard argument. The repro's object type — an index — does not exist in
+the standard at all, so 5.4 SR 4)b) cannot be cited for it. And where SR 4)b) does apply, the
+name at issue is a *reference*, so the standard's outcome would be that the reference denotes
+`<new schema>.public_tab` and the statement **errors** — not that it binds in `public`.
+`IndexStmt` (`parse_utilcmd.c:4194`) and `CreateTrigStmt` (`:4203`) are the only two element
+types whose `RangeVar` is a lookup rather than a creation name, and both are pre-d516974
+element types. a9c350d; no test (a test would have to invent a design decision).
+
+### S4 — user code run by an element can hijack the temporary `search_path`
+
+A plain (non-`LOCAL`) `SET search_path` executed by code an element invokes rewrites
+`CreateSchemaCommand`'s own GUC stack entry from `GUC_SAVE` to `GUC_SET`
+(`guc.c:2055-2065`), and `AtEOXact_GUC` restores only a `GUC_SAVE` entry (`guc.c:2212`), so `AtEOXact_GUC(true, save_nestlevel)` at `schemacmds.c:237` keeps the
+user's value: the private path leaks into the session, and every element after the hijack is
+created wherever the path now points, while the statement still reports `CREATE SCHEMA`.
+Reachable on `old` with bare built-ins and pre-d516974 element types only —
+`FOR VALUES IN (length(set_config('search_path','public',false)))`
+(`/home/nm/src/pg/csxrun/evidence/ADJ-C10-C-old.txt`). `DefineIndex` guards against exactly
+this (`indexcmds.c:1314-1319`, "Roll back any GUC changes executed by index functions, and keep subsequent changes local to this command");
+`CreateSchemaCommand` has no equivalent — but neither does `CREATE EXTENSION`, which uses the
+byte-identical idiom (`extension.c:1321/1362/1503`) and exhibits both halves inside one
+top-level statement on `pre` (`refute-C10.txt` TRANSCRIPT 5). a9c350d, at most; no test.
+
+### S5 — a forward reference inside one statement silently binds a different object
+
+Because the temporary path retains the caller's whole path, an element naming an object the
+same statement defines *later* binds a same-named object from the caller's path, while a later
+element binds the new one. Six instances measured on master (`H01-b-new.txt` G/H,
+`H01-e-new.txt` O1/O2, `H01-h-new.txt` S1b), and demonstrable on `old` with a `CREATE VIEW`
+element (`H01-c-*.txt` P5: binds `public.fwd` on new/old, `sfwd.fwd` on `pre`). a9c350d's own
+commit message says such forward references "used to work and no longer will"; the accurate
+statement is that they fail when nothing same-named is reachable and silently rebind when
+something is. What d516974 adds is blast radius: a wrong binding can now be frozen into an
+aggregate's `SFUNC`, an operator's implementation function, a TS dictionary's template or a
+`BEGIN ATOMIC` body rather than only a view's rewrite rule. No test.
+
+---
+
+## 5. Examined and found correct
+
+This is what makes the short findings list credible. Sixteen hunt lenses (H01-H16) and six
+recon dossiers ran roughly 300 probe transcripts across the three builds; the entries below
+are negative results, each backed by a cited transcript. The cassert server-log grep
+(`PANIC|FATAL|TRAP|Assert|server closed|terminating`) was empty for every run.
+
+**Grammar and reachability.** All 21 newly-reachable statement productions were executed and
+verified to reach execution (`R1-grammar-1.txt`, 26 cases d01-d18/f01-f06/n01-n02). Every command
+name in the doc's clause list is reachable from `schema_stmt` and vice versa
+(`H15-forms-new-1.txt`, 52 schemas created). `transformCreateSchemaStmtElements` handles
+exactly 12 node tags and the grammar produces exactly 12, so its `default: elog(ERROR,
+"unrecognized node type")` is unreachable — established by enumeration and by an empirical
+sweep (`H09-f` F3).
+
+**Catalog contents, dependencies, ownership.** For every `CREATE TYPE` spelling, `pg_type`
+(14 columns), `pg_range` and `pg_depend` are byte-identical inside vs outside `CREATE SCHEMA`
+(`H05-i-new.txt` S2). A complete side-by-side of all newly-allowed object types created
+inside `CREATE SCHEMA ... AUTHORIZATION regress_su` vs at top level under `SET ROLE regress_su`
+matches on all 23 catalog rows and all 13 `pg_shdepend` `deptype='o'` entries
+(`h02-h-new.txt` H3/H4). A `CREATE SCHEMA`-built domain records exactly the same dependency
+set as the fully-qualified outside control (`h06-f-new.txt` F1). Shell operators created via
+`COMMUTATOR`/`NEGATOR` land in the new schema, are owned by the `AUTHORIZATION` role, and
+round-trip through pg_dump (`H07-b-1.txt`, `H07-d-1.txt`).
+
+**pg_dump / pg_restore / pg_upgrade.** 28 elements built two ways — one `CREATE SCHEMA` and
+the same objects as separate commands under the same path — produce identical `pg_dump`
+output after schema-name normalisation, and a full dump → fresh database → re-dump is
+likewise identical, with a 92-row catalog census matching (`H13-dump-new-1.txt`). In both
+comparisons the *only* differing lines are pg_dump's random `\restrict`/`\unrestrict`
+nonce, which is regenerated per dump; the transcript labels those two diffs `DIFFERENT:` and
+`ROUND TRIP DIFFERS:` for that reason alone. A hard round trip
+with cross-referencing objects (`BEGIN ATOMIC` functions over a same-schema table and domain,
+an aggregate over an atomic function, an operator over it, a view over both, a TS
+parser+dictionary+configuration with a mapping, `COLLATION c2 FROM c1`, a range type with
+`SUBTYPE_OPCLASS`/`COLLATION`/`MULTIRANGE_TYPE_NAME`, and a shell operator left by an
+unresolvable `NEGATOR`) restores with zero errors and re-dumps identically
+(`H13-hardrt-new-2.txt`). pg_dump never emits `CREATE SCHEMA` with elements, so nothing
+round-trips through the new grammar. `pg_dump --binary-upgrade` → restore under `postgres -b`
+gives 0 restore errors, and preset binary-upgrade OIDs are consumed by the right objects
+inside `CREATE SCHEMA` exactly as outside (`H14-bu-1.txt`, `H14-bu-3.txt`).
+
+**Event triggers, deparse, hooks.** Every element type produces a
+`pg_event_trigger_ddl_commands()` row for the element's own object, with byte-identical
+`command_tag`, `object_type`,
+fully-qualified `object_identity`, correct `schema_name` (including reporting `public` for an
+element that lands in `public` via the S3 mechanism), `objsubid = 0`, and rows in source order
+(`h10-p1-new`, `h10-p8-new`, `h10-p3-new`). Side-effect objects — array types, multiranges,
+range constructors, implicit PK/unique indexes, domain constraints, the composite `pg_class`
+row, self-commutator shells — are reported identically inside and outside (`h10-p2-new`).
+`test_ddl_deparse` types and tags all 11 tested kinds correctly. `ProcessUtility_hook`,
+`OAT_POST_CREATE` and `OAT_NAMESPACE_SEARCH` see every sub-command individually, nested inside
+the `CREATE SCHEMA` (`h10-p5-new`). `in_extension` is correct for every element inside an
+extension script (`h10-p4b-new`).
+
+**Extensions.** The member set for a schema built inside `CREATE SCHEMA` in an extension
+script is byte-identical to the same objects built by top-level statements in the same script
+(`diff` empty); `DROP EXTENSION` leaves 0 orphans and 0 dangling `pg_depend` rows;
+`ALTER EXTENSION ... ADD/DROP` round-trips; `ALTER EXTENSION ... UPDATE` records 14 new
+members; the script's `search_path` is correctly restored after the element block
+(`H14-ext-1..5`).
+
+**GUC and identity handling.** `search_path` and `current_user` are restored after success,
+after failure, after `ROLLBACK TO SAVEPOINT`, after a caught plpgsql `EXCEPTION`, and after
+2000 caught failures in one `DO`; no "GUC nest level" warning was ever emitted
+(`h03-a-new.txt`, `H16-j-new.txt`, `H16-r-new.txt`). Odd `search_path` values (`''`, a
+nonexistent schema, embedded whitespace, quoting) behave. `SET LOCAL` inside an element is
+correctly discarded. `681d9e4`'s `NewGUCNestLevel`/`GUC_ACTION_SAVE` mechanism nests
+correctly for the new element types even under `SECURITY_RESTRICTED_OPERATION` +
+`RestrictSearchPath()` — the CVE-2023-2454 shape was re-run for this commit's object types
+inside `REFRESH MATERIALIZED VIEW` and is clean, with the restricted path intact immediately
+after the `CREATE SCHEMA` returns and every object in its own new schema
+(`h08-secrest-new.txt`).
+
+**Unusual outer contexts (E7).** 30 theories, all negative: plpgsql `EXECUTE` and static
+statements, SQL-language function bodies, `SECURITY DEFINER`, procedure transaction control,
+read-only SPI, savepoints, `PREPARE TRANSACTION` (an 11-element body prepares, commits and is
+fully usable), concurrent duplicate schema creation, lock timeouts mid-list, binary-upgrade
+mode, `debug_discard_caches = 1`, row triggers, event-trigger functions, extended query
+protocol, `BEGIN READ ONLY`, `SERIALIZABLE`, and single-user mode (`H14-ctx-*`, `H14-2pc-1`,
+`H14-bu-*`).
+
+**Diagnostics.** 73 adversarial statements covering every newly-allowed element type, each
+run inside `CREATE SCHEMA` and again at top level under an equivalent `search_path`, produced
+**0 differing diagnostics** (`H16-sweep-new.txt`). Error positions that are emitted are
+correct, including at byte offset ~215 000 for element #8001 (`H16-s-new.txt` S3) and inside
+an extension script, plpgsql static/`EXECUTE`, SQL functions and mid-way through a
+multi-statement simple query (`H14-pos-1.txt`).
+
+**Robustness.** 4000 `CREATE DOMAIN` elements take 0.13 s with `MessageContext` flat at
+64 KB; 100 000 elements ends with a clean "out of shared memory" hint; a 4000-deep domain
+chain works; 100-level recursive `CREATE SCHEMA` restores outer state; self-referential
+definitions all give ordinary errors; 1700-column composites and 101-argument functions give
+the documented limits identically inside and outside; a 10 000-label enum and a 1 MB function
+body are fine (`H16-d2/k/a-1/r-new.txt`).
+
+**Client tools.** ecpg preprocesses, compiles and runs a 28-object `CREATE SCHEMA` correctly;
+its two limitations (`$n` inside a dollar-quoted body; a host variable in a DDL element)
+reproduce identically outside `CREATE SCHEMA`. Independently re-verified this pass: 14
+element shapes preprocessed twice each (as a clause and standalone) all have `in == out`; the
+two failures (`BEGIN ATOMIC` bodies; a `###` operator name taken as a C preprocessor
+directive) fire identically outside (`CLIENT-ecpg-verify-new.txt`). psql's `\d`-family
+describe commands are byte-identical for an object made by an element vs by a standalone
+statement — `\df \da \do \dT \dD \dO \dF \dFd \dFp \dFt \d` and their `+` forms
+(`H12-desc-1-new.txt`). psql `--single-transaction`, `-c`, `\i`, `\ir`, `:var`
+interpolation, `\gexec`, `\if/\else/\endif`, `\g`, comments, dollar quotes and `COPY FROM
+STDIN` after a correctly-split `CREATE SCHEMA` all behave (`H11-*`, `H13-psqlmodes-new-1.txt`).
+
+**Composition (checked by the completeness critic, since no lens owned the seam).** A trigger
+function and its trigger in the same statement bind correctly; views, column `DEFAULT`s and
+`CHECK`s over sibling functions all deparse qualified; a forward reference from a trigger
+element to a later function element binds `public` — but so does the documented
+separate-commands equivalent, so it is symmetric. A sibling plpgsql trigger function whose
+unqualified `INSERT` writes to `public.audit_log` is likewise symmetric with the documented
+equivalent (`CRIT-2-compose-new.txt`, `CRIT-2b-trigger-new.txt`).
+
+**Bounded negatives worth recording.** Non-core PL validators run arbitrary code at DDL time
+inside the `CREATE SCHEMA` environment (a Perl `BEGIN` block fires during
+`plperl_validator`), but the channel is closed one layer down: PL/Perl refuses SPI during
+compilation, so that code cannot read `search_path`/`current_user`, cannot `SET search_path`
+to redirect later elements, and cannot `DROP SCHEMA` the schema being created — all three
+die with `ERROR: SPI functions can not be used during function compilation` and the whole
+statement rolls back (`CRIT-1-plperl-new.txt`). And
+`information_schema.domains.domain_default`'s frozen deparse (see §6, C09) is the *only*
+instance of that class in `information_schema`: `routines.routine_definition` is raw `prosrc`
+(`information_schema.sql:1589`), not a deparse at all, while
+`parameters.parameter_default` uses `pg_get_function_arg_default` and
+`attributes.attribute_default` uses `pg_get_expr`, both of which deparse at read time
+(`CRIT-4b-infoschema-new.txt` plus `information_schema.sql:294/1022/1188`).
+
+---
+
+## 6. Candidates investigated and rejected
+
+Nineteen candidates went through hunt → replicate → adjudicate → refute. Sixteen were
+rejected. This is the most useful section for judging how much to trust the four that
+survived: in almost every case the killer was the same control — *run the same thing outside
+`CREATE SCHEMA`, or on `pre`, and watch it fail identically*.
+
+| id | candidate | why rejected |
+|---|---|---|
+| C01 | Text-bodied routine element validated against the temporary path: accepted where the documented equivalent is rejected, then dead or silently wrong | Every symptom reproduces with a plain `SET search_path` and no `CREATE SCHEMA` (`refute-C01-p1-new.txt` T1-T6, T9), including the "strongest exhibit" — identical bodies binding differently for `BEGIN ATOMIC` vs quoted (T5). It is documented, intended behaviour of `AS '<text>'` bodies (`create_function.sgml:614-627`). The same server-manufactured invisible path has shipped in `CREATE EXTENSION` since 2011 (`extension.c:1362`) and produces all three symptoms on `pre` (`refute-C01-ext-pre.txt`). Two premises were false: the "accepted inside, rejected outside" asymmetry already holds on `old`/`pre` for `CREATE VIEW`, and the pre-d516974 element set is *not* entirely early-binding (a `nextval('sq'::text::regclass)` default is stored as a name and dies in a fresh session on `old` and `pre`). |
+| C02 | Aggregate `INITCOND`/`MINITCOND` validated under the temporary path and stored as raw text: unrestorable dump, pg_upgrade failure after `--check` said compatible | Reproduced with **no `CREATE SCHEMA`, the default `search_path`, and a fully qualified `INITCOND`** on `old`: `pg_dump` emits the aggregate before the table it names, restore loses it silently (`surviving_user_aggregates = 0`, psql exit 0), `pg_upgrade --check` says "Clusters are compatible" and the real run fails (`refuteC02-C-old.txt`). Inside and outside are indistinguishable down to `pg_depend` and pg_dump bytes (`refuteC02-D-new.txt` N1). The doc's own "equivalent way" qualifies internal cross-references; under the doc's actual recipe inside and outside agree exactly (`refuteC02-E-doc.txt`). |
+| C04 | Thesaurus sub-dictionary named in an element stored as unqualified raw text: dead outside the statement, retargetable, lost by pg_dump | The raw-text storage, the missing dependency, the retargetability and the pg_dump gap are all properties of `CREATE TEXT SEARCH DICTIONARY` itself: the whole chain reproduces on `pre` with nothing but `SET search_path = z, public` (`repl-C04-attr-pre.txt`). What is left attributable to d516974 is the loss of one create-time error message in one narrow shape, and the first symptom a user sees is a loud, immediate `text search dictionary "..." does not exist` on the very next statement. |
+| C05 | `checkSchemaNameList` guards only the element's own name, so elements create objects in other schemas including `pg_catalog` and `pg_temp` | The same statements standalone on master give byte-identical placement; the unqualified case is actually *better* inside. On `old`, pre-existing element types do everything the candidate calls new, and worse: `CREATE VIEW` over a temp table silently relocates the element's own object into `pg_temp`; a `CREATE TABLE` with a `pg_temp`-schema column type leaves a permanent table that loses its only column at session end. The headline damage (a permanent range type destroyed at session end) reproduces on `pre` with a standalone `CREATE TYPE`. |
+| C06 | An element can drop the schema being created; `CREATE SCHEMA` still reports success and `pg_dump` then fails for the whole database | The documented separate-commands form gives a byte-identical outcome in the same session, and reproduces verbatim on `old` and `pre`. It needs no aggregate, no `INITCOND`, no domain and no user code: two ordinary concurrent sessions (`BEGIN; CREATE FUNCTION s.f() ...` / `DROP SCHEMA s;` / `COMMIT;`) orphan a `pg_proc` row on `pre` and break `pg_dump` for the database (`refC06-B-pre.txt`). It is a 14-year-old explicitly-deferred gap: `1575fbc` (2012) fixed relations only and says "We need similar protection for all other object types ... I'm leaving that for a separate commit." |
+| C07 | Session temp namespace outranks the new schema (see §4 S2) | Identical outside `CREATE SCHEMA`, identical on `pre`, and documented twice (`config.sgml`, `create_function.sgml`). a9c350d at most; kept in §4 as context. |
+| C09 | Domain `DEFAULT` deparsed under the temporary path and stored unqualified, so `information_schema.domains` and `\dD` report a default meaning a different function | Reproduces with the plainest top-level `CREATE DOMAIN public.dd AS int DEFAULT f();` under the default path, byte-identically on `new` and `pre`, and every ordinary domain's `domain_default` is likewise not dump/restore-stable. Documented as known since `3666260` (2006) — "default values for domains really need to be dumped by decompiling the typdefaultbin expression, not just printing the typdefault text which may be out-of-date or assume the wrong schema search path" — and the sibling `adsrc`/`consrc` columns were deleted for the same reason in `fe50382`. The rule has exactly one input (is the referenced object visible in the live path), so "inside stores the wrong text, outside the right one" is false in both directions. |
+| C10 | `CreateSchemaCommand` does not defend its temporary path against user code (see §4 S4) | Full misplacement plus full leak on `old` with only built-ins and pre-d516974 element types; `CREATE EXTENSION` exhibits both halves on `pre` with a bare `SET` and no function at all. a9c350d at most. |
+| C11 | Unqualified names resolve differently from the documented equivalent in both directions | Part (a) is a9c350d (a VIEW/TABLE-only script gives the same wrong binding on `old`, the right one on `pre`); parts (b) and (c) are identical on all three builds. The doc's own "equivalent way" form and the prepended path outside `CREATE SCHEMA` produce the *same* binding as inside. |
+| C12 | Every `role`/`session_authorization` `SET`/`RESET` clause rejected under `AUTHORIZATION` (overlaps D2) | Not rejected as a phenomenon — it is D2 — but rejected as an *independent* candidate, because its own probes established that the rule violation predates the series: `adjC12-a3-pre.txt` reaches the byte-identical false message on `pre` using only pre-d516974-legal elements, and `adjC12-a4-pre.txt` reaches it with no `CREATE SCHEMA` at all inside a genuine SECURITY DEFINER function. §D2 carries the finding with that caveat stated. |
+| C13 | Shell/base types and TS parser/template cannot be created under `AUTHORIZATION <non-superuser>` even when the invoker is a superuser | Holding the effective user fixed — the one thing `AUTHORIZATION` is defined to change — the behaviour is byte-identical to top level under `SET ROLE`. 11 of 16 probed element forms succeed under `AUTHORIZATION regress_bob`; the 5 that fail do so through each command's own long-standing superuser gate (`typecmds.c:221`, `tsearchcmds.c:197/704`, `functioncmds.c:1162`), none of which d516974 touches. On `pre` a superuser was already blocked from two of the six then-allowed element types by naming a non-superuser in `AUTHORIZATION`. What survives is a documentation nit only. |
+| C15 | `DefineRange` multirange naming: an internal `elog` (XX000) and a raw unique-index violation reach the user; constructors land in the wrong schema | Real, but pre-existing (`6df7a96`, v14) and not about `CREATE SCHEMA`: a four-case probe that never uses `CREATE SCHEMA` is byte-identical on `new`, `old` and `pre`, and one statement with zero setup on `pre` produces `ERROR: 23505: duplicate key value ... (_r, 2200)`. Decisively, three of the four sub-observations require the two namespaces to differ, which *cannot* happen through `CREATE SCHEMA`'s own environment — an unqualified `MULTIRANGE_TYPE_NAME` resolves through the prepended path into the new schema. The cross-schema case is exercised by a shipped, passing in-tree test added five weeks *after* d516974 by the CVE-2026-6472 fix `4793fc4`. |
+| C16 | Tag-filtered event triggers are silently bypassed by wrapping a command in `CREATE SCHEMA` | The same bypass occurs with no `CREATE SCHEMA` anywhere, identically on all three builds: a `WHEN TAG IN ('CREATE SEQUENCE')` deny trigger does not fire for `CREATE TABLE t (a serial)`, and a `CREATE INDEX` deny trigger does not fire for `CREATE TABLE ... PRIMARY KEY`. The invariant is "tag-filtered `ddl_command_start` has never fired for nested DDL", from `5525e6c` (2013). PostgreSQL's own documented DDL-gating recipe (an *unfiltered* start trigger) still works, and at `ddl_command_end` nothing is hidden. Residue: a documentation patch to `doc/src/sgml/event-trigger.sgml:89-101`, whose exception list for `ddl_command_start` names only shared objects and event-trigger commands. |
+| C17 | The wrong-schema error for 20 of the 21 added forms has no `LINE`/caret | Outside `CREATE SCHEMA` these same commands are equally caret-less — only `CREATE TABLE` gets one at top level — so the documented equivalence is satisfied, and for three forms the inside diagnostic is strictly *better*. The commit states the limitation in its own comment (`parse_utilcmd.c:4323`, "Sadly, this also means we don't have a parse location to report") and its own regression test records the caret-less output as expected. Diagnostic-quality limitation, self-acknowledged; cosmetic. |
+| C18 | Tab completion, superset of D4 | The parts that survived are §D4; the rest was refuted (see D4's counterarguments): the invariant was never universal, un-anchoring is a documented backlog, and the "misleading table list" is generic pre-existing psql behaviour. |
+| C19 | `create_schema.sgml`'s "all the created objects will be owned by that user" is false for two newly added element types | The premise "before d516974 the claim held for every legal element type" is false: on `old`, `CREATE SCHEMA idxs1 AUTHORIZATION aj_bob CREATE INDEX carol_idx1 ON carol_tab (a)` produces an index owned by `aj_carol`, and on `pre` the sentence is already false via `CHECK` constraints and triggers. `CREATE TYPE ... AS RANGE` produces byte-identical catalogs inside and outside. Longstanding doc imprecision (the sentence is 3450fd08/2003), not this commit's. |
+| O2 | A `PGC_SUSET` GUC in a routine element's `SET` clause is checked against the `AUTHORIZATION` role, not the invoking superuser | **Adjudicated intended.** `CREATE FUNCTION topg1() ... SET log_min_duration_statement = 1` issued at top level *by that same non-superuser role* fails with the identical `ERROR: permission denied to set parameter "log_min_duration_statement"` (`PLAN-o2ctl-new.txt`), and matches `SET ROLE` in all four sub-cases including after `GRANT SET ON PARAMETER` (`h02-d-new.txt` D2). Since the documented contract is "objects will be owned by that user", checking against that user is coherent; there is no output a correct implementation demonstrably ought to produce, so no test was written. Contrast D2, where the top-level control *succeeds* for the same role. |
+| O4 | Unqualified `CREATE INDEX`/`CREATE TRIGGER` element binds outside the new schema | Behaviour confirmed, but "correct" is undecidable without inventing a design (error? bind in the new schema? create the index in the new schema over a `public` table?), and it is a9c350d's, sharing S1's mechanism. Reported as §4 S3; no test. |
+
+---
+
+## 7. Limitations — what was not examined
+
+Taken from the completeness critic's pass; each item is a real gap in this audit.
+
+**Newly-allowed command forms.** At the grammar-production level, none was skipped: all 21
+were executed, and every variant the coverage dossier listed as untested was later run by some
+lens (prefix/unary operators, pre-8.2 aggregate syntax, ordered-set / hypothetical-set /
+moving aggregates, `RETURNS TABLE`, the no-`RETURNS` OUT-parameter form, `WINDOW`,
+`TRANSFORM FOR TYPE`, `SUPPORT`, `LANGUAGE c` against a real shared library, all four
+`CREATE COLLATION` forms, `CREATE TYPE ... (LIKE=...)`, `CANONICAL`, the full domain
+`ColQualList`). Below production level, one gap survived to the critic and was then closed:
+no lens had used a non-core procedural language as an element `LANGUAGE` (plperl/plpython/pltcl
+*are* installed, contrary to one lens's stated gap) — closed, negative (§5).
+
+**Environment properties E1-E7.** E1, E2 and E7 are saturated. E3's second consequence
+(`isTopLevel` gating `PreventInTransactionBlock`) was closed by grep, not by a probe: no
+newly-allowed command calls it, so the only `isTopLevel`-sensitive element is the pre-existing
+`CREATE INDEX CONCURRENTLY`. E4's `canSetTag = false` and `planOrigin = PLAN_STMT_INTERNAL`
+were probed by nobody and closed by source reading: `planOrigin` has no in-core consumer, and
+the only out-of-core one (`pg_stat_statements.c:1487-1489`) tests only for
+`PLAN_STMT_CACHE_GENERIC`/`CUSTOM`. Four further E2/E7 sub-paths were answered only by
+reading source: `GucStack.srole` (not exposed to SQL); `push_old_value`'s
+`Assert(stack->state == GUC_SAVE)` for `GUC_ACTION_SAVE` (no input reaches it); whether any
+user-sensitive GUC *assign* hook can hold a stack entry at `CreateSchemaCommand`'s nest level;
+and whether a subtransaction opened inside an element leaves the GUC nest level inconsistent.
+
+**Not exercised at all.**
+* `sepgsql` — not built (no `--with-selinux`); `test_oat_hooks` was substituted, so
+ `OAT_POST_CREATE` for the ~15 new object types under E1's identity switch is unexercised by
+ a real MAC module.
+* An `InvokeNamespaceSearchHook` returning false for the new schema (`namespace.c:4281`). This
+ is the one code path that would silently relocate the creation namespace — S1's failure mode
+ by a second route, with no `$user` name required. Nobody wrote the C hook.
+* Any C-level SPI caller (`worker_spi.c:107` is the only in-tree SPI `CREATE SCHEMA` with
+ elements, and it was never run on any build).
+* `table_rewrite` event triggers (no allowed element can rewrite a table) and
+ `session_replication_role = replica` with `ENABLE REPLICA` triggers.
+* psql `\copy`, `\e`, `\ef`, `\ev`. `\e` re-feeds the edited buffer through `psql_scan`, which
+ is D1's wedge with an editor in the loop.
+* DDL-deparse consumers: no in-core deparser exists, so nobody could observe what an extension
+ deparsing a stashed element parse tree would emit. `checkSchemaNameRV`/`checkSchemaNameList`
+ only *validate*, so the stashed tree carries an unqualified name whose meaning depends on the
+ temporary path.
+* The interactive-terminal consequence of D1 was found only in the completeness pass; it did
+ **not** go through the refute stage, and it was not run on `pre`.
+
+**Structural weaknesses of the audit itself, stated so they can be discounted.** Ten of the
+sixteen lenses (H01, H02, H05, H07, H08, H09, H11, H12, H13, H15) wrote no coverage-gaps
+section at all — including the three that produced the most surviving material. Three of the
+six that did wrote gaps that were factually wrong about the environment (claiming
+`pg_stat_statements` unavailable when another lens had used it; claiming plperl/plpython not
+built when they are installed; claiming no way to run arbitrary user code at DDL time using
+only the new element types, when a plperl validator does exactly that). Lens boundaries were
+drawn per object type, so "new element × old element" compositions were owned by nobody until
+the completeness pass ran them (negative). Finally, the `new` install tree is contaminated:
+`/home/nm/src/pg/csx-inst/share/postgresql/extension/` still holds four lenses' leftover
+extensions (`csxfail`, `csxg`, `csxh05`, `csxok`), so any future probe enumerating
+`pg_available_extensions` on `new` will see them.
+
+---
+
+## 8. How to reproduce everything
+
+### Builds
+
+```
+/home/nm/src/pg/csx-audit 6885b845b4ba master, this audit branch (create-schema-defect-tests)
+/home/nm/src/pg/csx-old 404db8f9edbb d516974~1
+/home/nm/src/pg/csx-pre 1ff3180ca016 a9c350d~1
+```
+
+Installed trees: `/home/nm/src/pg/csx-inst`, `csx-old-inst`, `csx-pre-inst`.
+Build dirs: `/home/nm/src/pg/csx-build`, `csx-old-build`, `csx-pre-build`.
+
+### Running a probe on any build
+
+```
+/home/nm/src/pg/csxrun/pgrun.sh <new|old|pre> file.sql [more.sql ...] # SQL
+/home/nm/src/pg/csxrun/pgsh.sh <new|old|pre> script.sh # shell
+```
+
+Each spins up a throwaway cluster (`initdb -A trust`, `fsync=off`), runs the input with
+`psql -X -e -v ON_ERROR_STOP=0`, prints the output, destroys the cluster, and greps the server
+log for `PANIC|FATAL|TRAP|Assert`. Extra server options via `PGRUN_OPTS` (e.g.
+`PGRUN_OPTS="-c log_statement=all"`), extra initdb options via `PGRUN_INITDB_OPTS`.
+`pgsh.sh` exports `BIN`, `PGHOST`, `PGUSER`, `PGDATABASE` and puts `BIN` on `PATH`.
+**Always use `psql -X`**: the repository owner's `~/.psqlrc` enables per-statement rollback,
+which masks transaction-abort behaviour and would mislead on D1 in particular.
+
+### The four findings, one command each
+
+```
+# D1 (psql lexer) -- expect: VACUUM error, exit 3, cs_gb absent; oracle and control clean
+/home/nm/src/pg/csxrun/pgsh.sh new /home/nm/src/pg/csxrun/probe/client-c03/oracle.sh
+# D1 discriminator vs the case 049b742 deferred
+/home/nm/src/pg/csxrun/pgsh.sh new /home/nm/src/pg/csxrun/probe/xprem/p6.sh
+# D2 (SET role under AUTHORIZATION)
+/home/nm/src/pg/csxrun/pgrun.sh new /home/nm/src/pg/csxrun/probe/plan/o1o2.sql
+/home/nm/src/pg/csxrun/pgrun.sh pre /home/nm/src/pg/csxrun/probe/adjC12/a3.sql # attribution
+# D3 (PL/pgSQL) -- run on all three builds
+/home/nm/src/pg/csxrun/pgrun.sh new /home/nm/src/pg/csxrun/probe/adjC14/adj.sql
+/home/nm/src/pg/csxrun/pgrun.sh old /home/nm/src/pg/csxrun/probe/adjC14/adj.sql
+# D4 (tab completion) -- needs a pty; see the shipped TAP test below
+# S1 (a9c350d, "$user")
+/home/nm/src/pg/csxrun/pgrun.sh new /home/nm/src/pg/csxrun/probe/plan/oracle.sql
+/home/nm/src/pg/csxrun/pgrun.sh pre /home/nm/src/pg/csxrun/probe/plan/oracle3.sql
+```
+
+### The shipped tests
+
+Six files changed on branch `create-schema-defect-tests`, +339/-0, all test inputs or expected
+files; no compiled source is touched, so **no rebuild is needed**
+(`ninja -C /home/nm/src/pg/csx-build -n` reports "no work to do"):
+
+```
+src/test/regress/sql/create_schema.sql src/test/regress/expected/create_schema.out
+src/pl/plpgsql/src/sql/plpgsql_misc.sql src/pl/plpgsql/src/expected/plpgsql_misc.out
+src/bin/psql/t/001_basic.pl src/bin/psql/t/010_tab_completion.pl
+```
+
+```
+meson test -C /home/nm/src/pg/csx-build --suite setup
+meson test -C /home/nm/src/pg/csx-build regress/regress plpgsql/regress \
+ psql/001_basic psql/010_tab_completion psql/020_cancel psql/030_pager \
+ pg_upgrade/002_pg_upgrade recovery/027_stream_regress \
+ test_plan_advice/001_replan_regress --print-errorlogs
+```
+
+Expected: exactly the seven failures tabulated in §3 and nothing else. Diffs land in
+`/home/nm/src/pg/csx-build/testrun/<group>/<name>/regression.diffs`; TAP output in the
+corresponding `log/regress_log_*`. Every added expected-output block records what a
+**correct** implementation would print, so the failure diff *is* the demonstration.
+
+To drive one regress test standalone (avoiding the schedule):
+
+```
+B=/home/nm/src/pg/csx-build; S=/home/nm/src/pg/csx-audit/src/test/regress
+export PATH="$B/tmp_install/home/nm/src/pg/csx-inst/bin:$B/src/test/regress:$PATH"
+export LD_LIBRARY_PATH="$B/tmp_install/home/nm/src/pg/csx-inst/lib/x86_64-linux-gnu"
+export INITDB_TEMPLATE="$B/tmp_install/initdb-template"
+"$B/src/test/regress/pg_regress" --inputdir="$S" --expecteddir="$S" --bindir= \
+ --dlpath="$B/src/test/regress" --dbname=regression --outputdir=/tmp/csxrun \
+ --temp-instance=/tmp/csxrun/tmp_check --port=48460 create_schema
+```
+
+(for plpgsql: `--inputdir`/`--expecteddir` `/home/nm/src/pg/csx-audit/src/pl/plpgsql/src`,
+`--dlpath "$B/src/pl/plpgsql/src"`, `--dbname regression_plpgsql`, test `plpgsql_misc`).
+
+### Evidence
+
+Every transcript this report quotes is under `/home/nm/src/pg/csxrun/evidence/`; the probe
+sources that produced them are under `/home/nm/src/pg/csxrun/probe/`. The per-lens working
+notes, including the full theory lists and negative results summarised in §5, are under
+`/home/nm/src/pg/csxrun/notes/` (`H01`-`H16`, plus recon dossiers `R1`-`R6`). Key files:
+
+| finding | evidence |
+|---|---|
+| D1 | `CLIENT-C03-oracle-new.txt`, `CRIT-3-interactive-{new,old}.txt`, `XPREM-p6-new.txt`, `ADJ-C03-A-{new,old}.txt`, `refute-C03-run{1,2,3,4}*.txt`, `FINAL-tap.txt`, `FACTCHECK-D1-{oracle,xprem6,pty,var}-new.txt` |
+| D2 | `PLAN-o1o2-new.txt`, `PLAN-o2ctl-new.txt`, `PLAN-oracle-new.txt`, `VERIFY-o1-mechanism-new.txt`, `h02-a-{new,old,pre}.txt`, `adjC12-a3-pre.txt`, `FINAL-regressdiffs.txt`, `FACTCHECK-D2-{spellings,o1o2}-new.txt`, `FACTCHECK-D2-a3-pre.txt` |
+| D3 | `adjC14-{new,old,pre}-1.txt`, `PLAN-oracle4-new.txt`, `refC14-r{1,2,3,4}*.txt`, `XPREM-p{2,4,5}*.txt`, `FINAL-regressdiffs.txt`, `FACTCHECK-D3-{new,old}.txt` |
+| D4 | `CLIENT-O3-tap-new.txt`, `PLAN-o3-die-new.txt`, `refute-C18-{new,old,pre}-*.txt`, `FINAL-tap.txt` |
+| S1 | `PLAN-oracle-new.txt`, `PLAN-oracle3-pre.txt`, `adjC08-{new,old,pre}.txt`, `refC08-r{3..8}*.txt`, `replC08-*.txt` |
+| test run | `FINAL-suiterun.txt`, `FINAL-regressdiffs.txt`, `FINAL-tap.txt`, `FINAL-gitdiff.txt`, `VERIFY-*`, `FACTCHECK-suiterun.txt` |
diff --git a/PROVENANCE.md b/PROVENANCE.md
new file mode 100644
index 0000000..19b314d
--- /dev/null
+++ b/PROVENANCE.md
@@ -0,0 +1,305 @@
+# PROVENANCE
+
+Branch `create-schema-defect-tests` is the product of an automated, model-driven audit of
+PostgreSQL commit `d516974` ("Support more object types within CREATE SCHEMA."). Everything
+on it -- the report and the regression tests -- was written by a language model. **No human
+wrote any of it, and no human has reviewed it.** This file records how it was produced so
+that a reviewer can judge the work and reproduce the verification.
+
+---
+
+## Tooling
+
+* **Tool:** Claude Code (Anthropic's agentic CLI), using its Workflow orchestration feature
+ (one deterministic script driving many subagents).
+* **Model:** Opus 5 (`claude-opus-5`), for the orchestrator and for every subagent.
+* **Run by:** the repository owner (Noah Misch), interactively, from
+ `/home/nm/src/pg/postgresql`.
+* **Workflow run ID:** `wf_784c5f4f-e9b`. Started 2026-09-02 23:19 UTC; relaunched
+ 23:33 UTC after the orchestrator rewrote the scheduler (see [Scheduling](#scheduling)).
+* **Human contribution:** the two prompts quoted below, and nothing else. The owner supplied
+ no finding, no verdict, and no line of any test.
+
+## The prompt
+
+Verbatim, as typed by the repository owner:
+
+```
+Make a large workflow, with at most 90 agents, to write test cases covering
+user-visible defects in commit d516974 that are still present in master. Use
+your own worktree; disregard the present dir except as repository to which to
+attach your worktree. The workflow should first look for extant user-visible
+defects. If it finds any, write a test case covering some of those defects.
+If any defects found weren't suitable to test, describe them in a report.
+
+The workflow should manage budget so, when it reaches a budget stop, it loses
+1-2 agents instead of 16 concurrent agents.
+
+Commit the following on a fresh branch:
+- A report describing any defects found, testable or not. Prefix the report
+ with [no defects] if that's so.
+- Any tests written
+- A PROVENANCE.md file containing model, prompt, etc.
+
+For this one, I'm wary that CREATE SCHEMA is a weird execution environment.
+I'm reminded of CVE-2023-2454 resulting from the weird environment. Commit
+d516974 means a bunch more old code needs to be ready for that weird
+environment. (Granted, the CVE-2023-2454 fix made the environment less weird.
+If we're lucky, all is now well.)
+```
+
+A second message corrected the first implementation of the budget requirement:
+
+```
+That's a terrible implementation of the budget goal. Now I won't even use my
+budget before it expires. If you can't do better than that, just run full 16
+concurrency and accept the loss.
+
+I wanted something like: start at 16 concurrent, but when one exits, estimate
+the budget burndown curve. If there's a decent chance one started now would
+fail due to budget exhaustion, don't replace the one that just exited.
+```
+
+## Environment
+
+* **Base commit:** `6885b845b4ba0b7aee09daa9817703477faa3704` -- "doc: Fix link on
+ pg_dsm_registry_allocations page." This branch is based on it; every `file:line`
+ citation in the report is against it.
+* **Audited commit:** `d516974840f4059d331ae6057ede3e4edd3c6747` -- "Support more object
+ types within CREATE SCHEMA." (Kirill Reshke, Jian He, Tom Lane; 2026-04-06).
+* **Worktree:** `/home/nm/src/pg/csx-audit`, a `git worktree` attached to the owner's clone
+ at `/home/nm/src/pg/postgresql`.
+* **Builds**, all meson, all `-Dcassert=true -Ddebug=true -Doptimization=1
+ -Dtap_tests=enabled`:
+
+ | name | source | purpose |
+ |---|---|---|
+ | `new` | `6885b84` (this worktree) | every "master" observation in the report |
+ | `old` | `404db8f` = `d516974~1` | the `is_new` dimension for d516974 itself |
+ | `pre` | `1ff3180` = `a9c350d~1` | before the whole CREATE SCHEMA series, so findings can be attributed to d516974 rather than to its two same-day siblings a9c350d and 404db8f |
+
+* **Harness:** `/home/nm/src/pg/csxrun/pgrun.sh <new|old|pre> file.sql ...` initdb's a
+ throwaway cluster under `/tmp`, starts it with `fsync=off` and a unix socket only, runs each
+ file with `psql -X -e -v ON_ERROR_STOP=0`, prints the transcript plus any PANIC/FATAL/TRAP
+ lines from the server log, then destroys the cluster. `pgsh.sh <new|old|pre> script.sh`
+ does the same but runs an arbitrary shell script with `BIN`, `PGHOST`, `PGUSER`,
+ `PGDATABASE` exported, for pg_dump round-trips, ecpg, and psql-lexer tests.
+* **SQL standard text:** `/home/nm/src/pg/gbyrun/std/found.txt`, pdftotext output of
+ `5CD2-02-Foundation-2006-01.pdf` (the 2006-01 committee draft of ISO/IEC 9075-2, which has
+ the General Rules). 11.1 `<schema definition>` begins near line 25895.
+* **Host:** Debian 13 (trixie), Linux 6.12.94 x86_64, gcc (Debian 14.2.0-19) 14.2.0,
+ meson 1.7.0, ninja 1.12.1, Python 3.13.5. Two effective CPUs (cpuset cgroup).
+* **Not part of the deliverable:** `/home/nm/src/pg/csxrun/` (recon dossiers, per-lens notes,
+ every raw transcript) and the build and install trees. Nothing on this branch depends on
+ them except the reproduction commands in the report, which a reviewer would re-create.
+
+---
+
+## The rubric
+
+The owner had corrected an earlier audit for reporting *changes* rather than *defects*
+("You're not here to be a change detector, you're here to find defects"), and gave this
+rule, which was written into the prompt of every agent in this run:
+
+Each candidate is scored on three independent dimensions:
+
+* `sql_mismatch` -- the SQL standard requires behavior master does not exhibit;
+* `per_principles` -- the commit's own principles (its commit message, the code comments it
+ wrote, the documentation it wrote, and the proposition that a command documented as
+ accepted inside CREATE SCHEMA should behave there as it does outside, apart from landing in
+ the new schema) require behavior master does not exhibit;
+* `is_new` -- the RULE VIOLATION, not the syntax, is new relative to the state before
+ d516974.
+
+**Defect iff `per_principles OR (sql_mismatch AND is_new)`.** `is_new` alone is not a
+defect. `sql_mismatch AND NOT is_new` is a pre-existing divergence that d516974 did not
+cause. The script recomputes `is_defect` from each adjudicator's own returned booleans and
+logs a warning when the adjudicator's prose disagrees with its own dimensions.
+
+Agents were also warned, in the shared prompt, against the specific failure that wrecked the
+earlier audit: reaching for a standard citation to dignify a finding that is really about
+internal coherence. Aggregates, operators, text search objects and indexes are outside the
+standard's ken, so `sql_mismatch` is false for them by construction.
+
+## Attribution
+
+d516974 landed the same day as two siblings, `a9c350d` ("Don't try to re-order the
+subcommands of CREATE SCHEMA") and `404db8f` ("Execute foreign key constraints in CREATE
+SCHEMA at the end"), and was followed by `049b742` ("psql: Tighten heuristics for BEGIN/END
+within CREATE SCHEMA"). a9c350d is the commit that made CREATE SCHEMA rely on a temporarily
+prepended `search_path` instead of rewriting each element's schema name, so several
+observations in this area belong to it, not to d516974. The `pre` build exists to settle
+that question empirically, and the report keeps sibling-commit findings in a separate
+section.
+
+## Method
+
+Nine phases, driven by one deterministic script (preserved at
+`~/.claude/projects/.../workflows/scripts/create-schema-d516974-audit-wf_784c5f4f-e9b.js`):
+
+1. **Recon** (6 agents) -- commit forensics and the commit's own principles; the execution
+ environment at code level; the backend code newly reachable from inside CREATE SCHEMA;
+ the client-side code (psqlscan.l, tab completion, ecpg); the SQL standard's
+ `<schema definition>` rules; and existing test coverage. Each wrote a dossier that later
+ phases read instead of redoing the work.
+2. **Hunt** (16 agents, one per lens) -- each lens generated at least twelve falsifiable
+ theories and then *ran* them on all three builds, reporting only observations it could
+ quote from a real transcript, plus a list of what it checked and found correct.
+3. **Triage** (1 agent) -- dedupe into a numbered candidate list, spot-checking claims
+ against their evidence files.
+4. **Replicate** -- an independent agent per candidate re-derived the observation from
+ scratch, ran it on `new`, `old` and `pre`, ran the equivalent command *outside* CREATE
+ SCHEMA as a control, minimized the reproduction, and named the mechanism with file:line.
+5. **Adjudicate** -- one agent per replicated candidate applied the rubric.
+6. **Refute** -- one adversarial agent per surviving defect, instructed to kill it and to
+ default to refuted when uncertain.
+7. **Coherence** (2 agents) -- a shared-premise pass (when several findings rest on one
+ claim, adjudicate the claim once, adversarially) and a completeness critic whose answer
+ becomes the report's limitations section.
+8. **Tests** (5 agents, serial) -- plan, write core regression tests, write client-side
+ tests, adversarially verify, polish.
+9. **Report** (2 agents) -- write, then fact-check every quoted transcript, file:line
+ citation and standard citation by re-running and re-reading.
+
+Phases 4 to 6 run as one pipeline per candidate, so a candidate can be under refutation
+while another is still being replicated.
+
+## Scheduling
+
+The owner asked that a budget stop cost 1-2 in-flight agents rather than 16. The first
+implementation ran everything in fixed chunks of two, which he rejected: it would have left
+the budget unused when the session window expired. His correction asked for an adaptive
+scheme, and that is what shipped.
+
+Each phase runs on a worker pool that fills to 16 immediately -- every worker takes its first
+item unconditionally -- and then gates only *replacements*. When a worker finishes an item
+it launches another only if
+
+```
+budget.remaining() >= ((inFlight + 1) + reserve) * estCostPerAgent * 1.2
+```
+
+otherwise it retires permanently, so concurrency tapers 16 -> ... -> 1 as the budget drains
+and the run ends with one agent in flight instead of sixteen. `estCostPerAgent` is
+`(budget.spent() - baselineAtStart) / liveCompletions`, with a 45k-output-token prior until
+three live agents have finished; `budget.spent()` is a cumulative session-shared counter, so
+it is baselined per invocation, and a completion counts as "live" only if its surrounding
+delta exceeds 2k output tokens, so cached replays on resume do not drag the estimate to zero.
+`reserve` holds back capacity for phases that still must run (8 agents through Hunt and the
+candidate chains, 7 through Coherence) so the taper starts early enough that the report still
+gets written. Retirement is logged, and a pool that stops with items unstarted logs
+`POOL STOPPED EARLY: n/m`; resuming the run ID replays completed agents from the
+content-addressed cache and runs only the remainder.
+
+**This run took the fallback branch the owner authorized.** `budget.remaining()` is finite
+only when a token target is set for the turn; none was, so it was `Infinity`, the gate always
+passed, and the run executed as a flat 16-wide pool. Measured 7 minutes into the Hunt phase:
+24 agent transcripts, 15 running concurrently. The taper code is present and engages
+whenever a target is set.
+
+## What the orchestrator did itself
+
+Outside the workflow, the orchestrating model: built the three trees and the harness; found
+four seed observations by hand and put them in the shared prompt so agents would extend
+rather than rediscover them; and independently checked two headline recon claims with
+explicit outside-CREATE-SCHEMA controls, confirming one and refuting the other
+(`csxrun/evidence/orch-verify1.txt`). It wrote this file. It did not write the report or
+any test.
+
+## Outcome
+
+* **83 subagents**, 0 errors, 8,252,907 subagent output tokens, 2,299 tool calls, 2h59m
+ wall clock (workflow run `wf_784c5f4f-e9b`, three invocations -- see
+ [Interruptions](#interruptions)).
+* 6 recon dossiers -> 16 hunt lenses -> **72 raw observations** against roughly 450 things
+ each lens checked and found correct -> **19 deduped candidates** -> 3 survived the
+ refute stage.
+* The report presents **4 defects attributable to d516974** (D1-D4), a separate section of
+ findings belonging to the sibling commit `a9c350d`, a section on what was examined and
+ found correct, and the 16 rejected candidates with the reason each was rejected.
+* The report writer reinstated narrowed forms of two candidates the refute stage had
+ killed (the `SET role` clause, D2, and tab completion, D4). It did so openly: D2 records
+ `is_new: true for this spelling, false for the rule` and carries an Attribution paragraph
+ saying the rule violation is reachable on `pre` by a harder route, and D4 carries a
+ "Counterarguments a reviewer should weigh" block. Both rest on `per_principles = true`,
+ which is sufficient under the rubric on its own. A reader who disagrees with that reading
+ should read those two blocks first.
+
+## Tests on this branch
+
+There is **no fix on this branch**, so the tests encode what a *correct* implementation
+would print and therefore **fail on master by design**. The failure diff is the
+demonstration. 339 lines were added across six test files; no non-test file was touched.
+
+Verified by the orchestrator, not by an agent:
+
+* With the additions: `regress/regress` (create_schema), `plpgsql/regress` (plpgsql_misc),
+ `psql/001_basic` (subtests 135-137) and `psql/010_tab_completion` (subtests 110-111) fail,
+ each only on the new material. Three further meson tests -- `pg_upgrade/002_pg_upgrade`,
+ `recovery/027_stream_regress`, `test_plan_advice/001_replan_regress` -- fail collaterally
+ because each runs the core `parallel_schedule`.
+* Each added block ships its own control, and every control passes: `001_basic` subtests
+ 138-140 (the same input with a non-routine clause splits correctly) and
+ `010_tab_completion` subtest 112 (the same completion works at top level).
+* With the six files stashed, all six suites are green: 243 + 13 + 134 + 101 + 2 + 5
+ subtests pass, 0 failures.
+
+Reproduce:
+
+```
+export TMPDIR=/home/nm/src/pg/csxrun/tmp
+meson test -C /home/nm/src/pg/csx-build --suite regress --suite plpgsql --suite psql \
+ --num-processes 1
+```
+
+## Interruptions
+
+The run was stopped and resumed twice. Completed agents replay from a content-addressed
+cache keyed on (prompt, opts), so each stop cost only the agents in flight; of 25 starts
+across the first two invocations only 2 keys were started twice.
+
+1. After the first two recon agents, to replace the rejected chunk-of-two scheduler with the
+ adaptive pool described above.
+2. After the hunt phase, on noticing that 24 candidates times 3 chain agents would exhaust
+ the agent cap before the test and report phases could run. The fix raised the cap to the
+ owner's stated 90, capped triage at 20 candidates, and added a guard that stops the chain
+ phase from taking a new candidate once `SPAWNED + 3` would cross `90 - 9`, permanently
+ reserving 9 agents for coherence, tests and the report.
+
+## A host condition that contaminates some agent transcripts
+
+`/tmp` on this machine is a **3.9 GB tmpfs**, and it reached 100% full (27 MB free) partway
+through the run, from unrelated work in other directories. `pgrun.sh` and `pgsh.sh`
+originally created their throwaway clusters under `/tmp`, so during that window `initdb`
+failed with
+
+```
+FATAL: could not write to file "base/4/2675": No space left on device
+```
+
+Any such line in an evidence transcript is that condition, **not** a PostgreSQL defect and
+not a finding. The harness was repointed to `/home/nm/src/pg/csxrun/tmp` (on `/`, which had
+22 GB free) and the affected checks were re-run. Nothing was deleted from `/tmp`. A
+reviewer re-running the suites should set `TMPDIR` to a disk-backed directory, because
+`pg_regress` puts its socket directory under `TMPDIR`.
+
+## What the orchestrator verified by hand
+
+Independently of the agents, and recorded in `csxrun/evidence/orch-verify*.txt`:
+
+* **D1 reproduced from scratch, with a control the agents had not run in that form.** On
+ `new`, `CREATE SCHEMA m CREATE FUNCTION f() ... GRANT ALL ON SCHEMA m TO begin;` followed
+ by `VACUUM;` yields `ERROR: VACUUM cannot run inside a transaction block` and the next
+ statement never runs, proving psql merged them into one message; on `old` the element is a
+ syntax error and splitting is correct; and replacing the routine clause with
+ `CREATE VIEW v AS SELECT 1` makes the identical GRANT split correctly.
+* **`SET search_path FROM CURRENT` confirmed** to capture the internal temporary path
+ (`sp1, "$user", public`) where the session value is `"$user", public`.
+* **One recon claim refuted**: that `CREATE SCHEMA` causes `pg_type.typdefault` to be stored
+ unqualified. A control -- create the schema and function first, `SET search_path = sm,
+ public`, then `CREATE DOMAIN sm.dm AS int DEFAULT dflt()` -- produces the identical split
+ outside `CREATE SCHEMA`, so it is pre-existing deparse behaviour. Three separate hunt
+ lenses had reported it; the pipeline dropped it.
+* **The four highest-severity rejections and the two adjudications that bypassed the refute
+ stage were read and checked**, and their reasoning holds.
+* **The test outcome above**, both with and without the additions.
diff --git a/src/bin/psql/t/001_basic.pl b/src/bin/psql/t/001_basic.pl
index 028df33..189f82c 100644
--- a/src/bin/psql/t/001_basic.pl
+++ b/src/bin/psql/t/001_basic.pl
@@ -577,4 +577,44 @@ psql_fails_like(
qr/wrong key/,
'\unrestrict does not do backquote expansion');
+#
+# AUDIT ADDITION: a user-visible defect of commit
+# d516974840f4059d331ae6057ede3e4edd3c6747 ("Support more object types within
+# CREATE SCHEMA."). The first case below records the behavior that a CORRECT
+# implementation would produce, so it FAILS against master; the failure is
+# the demonstration of the defect. The second case is a passing control.
+#
+# FINDING C03 (new in d516974). Correct behavior: psql ends a CREATE SCHEMA
+# statement at its semicolon, so the VACUUM and the SELECT that follow it are
+# separate queries and both run.
+# Currently FAILS: the CREATE FUNCTION clause arms psqlscan.l's BEGIN/END
+# counter, and psqlscan.l clears that per-clause state only on the token
+# "create" -- GRANT is the one CREATE SCHEMA clause not starting with CREATE.
+# So the view named "begin" in the GRANT clause is counted as the start of a
+# routine body, and psql sends all three statements as one query: the VACUUM
+# then fails inside the resulting implicit transaction block and takes the
+# CREATE SCHEMA down with it.
+psql_like(
+ $node,
+ qq{CREATE SCHEMA cs_gb
+ CREATE VIEW begin AS SELECT 1 AS one
+ CREATE FUNCTION cs_gb_f() RETURNS int LANGUAGE sql AS 'select 1'
+ GRANT SELECT ON begin TO public;
+VACUUM;
+SELECT 'split ok' AS result;},
+ qr/^split ok$/m,
+ 'CREATE SCHEMA ends at its semicolon');
+
+# Control: the same shape with no routine clause, which splits correctly.
+psql_like(
+ $node,
+ qq{CREATE SCHEMA cs_gc
+ CREATE VIEW begin2 AS SELECT 1 AS one
+ CREATE VIEW cs_gc_v AS SELECT 1
+ GRANT SELECT ON begin2 TO public;
+VACUUM;
+SELECT 'split ok' AS result;},
+ qr/^split ok$/m,
+ 'control: CREATE SCHEMA with no routine clause');
+
done_testing();
diff --git a/src/bin/psql/t/010_tab_completion.pl b/src/bin/psql/t/010_tab_completion.pl
index 64e27ef..099ac3d 100644
--- a/src/bin/psql/t/010_tab_completion.pl
+++ b/src/bin/psql/t/010_tab_completion.pl
@@ -126,6 +126,29 @@ sub clear_line
return;
}
+# Like check_completion(), but report a test failure rather than dying when
+# the expected completion does not happen. (check_completion() waits for its
+# pattern and dies on timeout, which would abort the rest of this file.) The
+# ^U and \r sent after the input also take the place of clear_query().
+sub check_completion_nofail
+{
+ my ($send, $pattern, $annotation) = @_;
+
+ # report test failures from caller location
+ local $Test::Builder::Level = $Test::Builder::Level + 1;
+
+ my $out = $h->query_until(qr/Query buffer reset.*postgres=# $/s,
+ $send . "\025\\r\n");
+ my $okay = ($out =~ $pattern);
+ ok($okay, $annotation);
+ # for debugging, log actual output if it didn't match
+ local $Data::Dumper::Terse = 1;
+ local $Data::Dumper::Useqq = 1;
+ diag 'Actual output was ' . Dumper($out) . "Did not match \"$pattern\"\n"
+ if !$okay;
+ return;
+}
+
# check basic command completion: SEL<tab> produces SELECT<space>
check_completion("SEL\t", qr/SELECT /, "complete SEL<tab> to SELECT");
@@ -460,6 +483,78 @@ check_completion("FR\t", qr/FROM /,
clear_query();
+#
+# AUDIT ADDITION: a user-visible defect of commit
+# d516974840f4059d331ae6057ede3e4edd3c6747 ("Support more object types within
+# CREATE SCHEMA."). The two check_completion_nofail() cases below record the
+# completions that a CORRECT implementation would offer, so they FAIL against
+# master; the failures are the demonstration of the defect. The four
+# check_completion() cases before them and the two after are passing controls.
+#
+
+# Two clause types that d516974 did convert to TailMatches(), so that their
+# completions fire inside CREATE SCHEMA.
+check_completion(
+ "CREATE SCHEMA s CREATE DOMAIN d A\t",
+ qr/CREATE DOMAIN d AS /,
+ "complete CREATE DOMAIN inside CREATE SCHEMA");
+
+clear_query();
+
+check_completion(
+ "CREATE SCHEMA s CREATE TEXT SEARCH CONF\t",
+ qr/TEXT SEARCH CONFIGURATION /,
+ "complete CREATE TEXT SEARCH inside CREATE SCHEMA");
+
+clear_query();
+
+# d516974 also added COLLATION and TYPE to the clause list psql offers after
+# CREATE SCHEMA ... CREATE, so psql agrees that both are legal there.
+check_completion(
+ "CREATE SCHEMA s CREATE COLL\t",
+ qr/CREATE SCHEMA s CREATE COLLATION /,
+ "complete CREATE SCHEMA ... CREATE COLL<tab> to COLLATION");
+
+clear_query();
+
+check_completion(
+ "CREATE SCHEMA s CREATE TY\t",
+ qr/CREATE SCHEMA s CREATE TYPE /,
+ "complete CREATE SCHEMA ... CREATE TY<tab> to TYPE");
+
+clear_query();
+
+# FINDING O3 (new in d516974). Correct behavior: having offered COLLATION and
+# TYPE as clauses just above, psql goes on completing them inside CREATE
+# SCHEMA the way it does at top level (the two controls after these).
+# Currently FAILS: those two rules still use anchored Matches()/HeadMatches(),
+# which cannot match once "CREATE SCHEMA s" precedes them; d516974 converted
+# only CREATE DOMAIN and CREATE TEXT SEARCH.
+check_completion_nofail(
+ "CREATE SCHEMA s CREATE COLLATION c FR\t",
+ qr/CREATE COLLATION c FROM /,
+ "complete CREATE COLLATION inside CREATE SCHEMA");
+
+check_completion_nofail(
+ "CREATE SCHEMA s CREATE TYPE t AS EN\t",
+ qr/CREATE TYPE t AS ENUM /,
+ "complete CREATE TYPE inside CREATE SCHEMA");
+
+# Controls: the same two completions at top level, which do work.
+check_completion(
+ "CREATE COLLATION c FR\t",
+ qr/CREATE COLLATION c FROM /,
+ "complete CREATE COLLATION at top level");
+
+clear_query();
+
+check_completion(
+ "CREATE TYPE t AS EN\t",
+ qr/CREATE TYPE t AS ENUM /,
+ "complete CREATE TYPE at top level");
+
+clear_query();
+
# send psql an explicit \q to shut it down, else pty won't close properly
$h->quit or die "psql returned $?";
diff --git a/src/pl/plpgsql/src/expected/plpgsql_misc.out b/src/pl/plpgsql/src/expected/plpgsql_misc.out
index ffb377f..87c4585 100644
--- a/src/pl/plpgsql/src/expected/plpgsql_misc.out
+++ b/src/pl/plpgsql/src/expected/plpgsql_misc.out
@@ -29,6 +29,41 @@ CREATE OR REPLACE PROCEDURE public.test2(IN x integer)
BEGIN ATOMIC
SELECT (x + 2);
END
+--
+-- AUDIT ADDITION: a user-visible defect of commit
+-- d516974840f4059d331ae6057ede3e4edd3c6747 ("Support more object types within
+-- CREATE SCHEMA."). Unlike every case above, this one records the output
+-- that a CORRECT implementation would produce, so it FAILS against master;
+-- the failure diff is the demonstration of the defect.
+--
+-- FINDING C14 (new in d516974). Correct behavior: a CREATE SCHEMA whose
+-- element is a routine with a BEGIN ATOMIC body is writable as a direct
+-- statement in PL/pgSQL, exactly like the same routine outside CREATE SCHEMA
+-- above, and creates misc_schema.test3().
+-- Currently FAILS: make_execsql_stmt() counts BEGIN/END only after a leading
+-- CREATE [OR REPLACE] {FUNCTION|PROCEDURE}, never after CREATE SCHEMA, so it
+-- ends the statement at the first semicolon inside the routine body. psql's
+-- copy of this heuristic learned about CREATE SCHEMA in d516974 and 049b742;
+-- pl_gram.y's copy did not.
+do
+$$
+ begin
+ create schema misc_schema
+ create function test3() returns int
+ begin atomic
+ select 3 + 3;
+ end;
+ end
+$$;
+\sf misc_schema.test3
+CREATE OR REPLACE FUNCTION misc_schema.test3()
+ RETURNS integer
+ LANGUAGE sql
+BEGIN ATOMIC
+ SELECT (3 + 3);
+END
+drop schema misc_schema cascade;
+NOTICE: drop cascades to function misc_schema.test3()
-- Test %TYPE and %ROWTYPE error cases
create table misc_table(f1 int);
do $$ declare x foo%type; begin end $$;
diff --git a/src/pl/plpgsql/src/sql/plpgsql_misc.sql b/src/pl/plpgsql/src/sql/plpgsql_misc.sql
index 0bc39fc..d11f9a1 100644
--- a/src/pl/plpgsql/src/sql/plpgsql_misc.sql
+++ b/src/pl/plpgsql/src/sql/plpgsql_misc.sql
@@ -21,6 +21,37 @@ $$;
\sf test1
\sf test2
+--
+-- AUDIT ADDITION: a user-visible defect of commit
+-- d516974840f4059d331ae6057ede3e4edd3c6747 ("Support more object types within
+-- CREATE SCHEMA."). Unlike every case above, this one records the output
+-- that a CORRECT implementation would produce, so it FAILS against master;
+-- the failure diff is the demonstration of the defect.
+--
+-- FINDING C14 (new in d516974). Correct behavior: a CREATE SCHEMA whose
+-- element is a routine with a BEGIN ATOMIC body is writable as a direct
+-- statement in PL/pgSQL, exactly like the same routine outside CREATE SCHEMA
+-- above, and creates misc_schema.test3().
+-- Currently FAILS: make_execsql_stmt() counts BEGIN/END only after a leading
+-- CREATE [OR REPLACE] {FUNCTION|PROCEDURE}, never after CREATE SCHEMA, so it
+-- ends the statement at the first semicolon inside the routine body. psql's
+-- copy of this heuristic learned about CREATE SCHEMA in d516974 and 049b742;
+-- pl_gram.y's copy did not.
+do
+$$
+ begin
+ create schema misc_schema
+ create function test3() returns int
+ begin atomic
+ select 3 + 3;
+ end;
+ end
+$$;
+
+\sf misc_schema.test3
+
+drop schema misc_schema cascade;
+
-- Test %TYPE and %ROWTYPE error cases
create table misc_table(f1 int);
diff --git a/src/test/regress/expected/create_schema.out b/src/test/regress/expected/create_schema.out
index b9ae4c4..344741a 100644
--- a/src/test/regress/expected/create_schema.out
+++ b/src/test/regress/expected/create_schema.out
@@ -322,5 +322,83 @@ 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)
+--
+-- AUDIT ADDITIONS: user-visible defects of commit
+-- d516974840f4059d331ae6057ede3e4edd3c6747 ("Support more object types within
+-- CREATE SCHEMA."). Unlike every case above, each case below records the
+-- output that a CORRECT implementation would produce, so each one FAILS
+-- against master; the failure diff is the demonstration of the defect.
+--
+-- FINDING O1 (new in d516974). Correct behavior: a SET clause for "role" or
+-- "session_authorization" in a CREATE FUNCTION element behaves just as it
+-- does outside CREATE SCHEMA, so both functions below are created in the new
+-- schema, owned by the AUTHORIZATION role, with the setting in proconfig.
+-- Currently FAILS: CreateSchemaCommand runs the elements with
+-- InLocalUserIdChange() true, so guc.c's GUC_NOT_WHILE_SEC_REST test rejects
+-- the SET clause, naming a security-definer function that is not there. Two
+-- call paths reach that test and both need dealing with: CreateFunction ->
+-- GUCArrayAdd -> validate_option_array_item, which only validates
+-- (changeVal = false) and is what fails today, and then ProcedureCreate ->
+-- ProcessGUCArray, which applies proconfig around the language validator. A
+-- fix must not simply let the value through: SetSessionAuthorization()
+-- asserts that the security restriction context is clear.
+CREATE SCHEMA regress_schema_setrole AUTHORIZATION regress_create_schema_role
+ CREATE FUNCTION cs_setrole() RETURNS int LANGUAGE sql
+ SET role = 'regress_create_schema_role' AS 'SELECT 1'
+ CREATE FUNCTION cs_setsess() RETURNS int LANGUAGE sql
+ SET session_authorization = 'regress_create_schema_role' AS 'SELECT 2';
+SELECT p.proname, pg_get_userbyid(p.proowner) AS owner, p.proconfig
+ FROM pg_proc p JOIN pg_namespace n ON n.oid = p.pronamespace
+ WHERE n.nspname = 'regress_schema_setrole' ORDER BY 1;
+ proname | owner | proconfig
+------------+----------------------------+----------------------------------------------------
+ cs_setrole | regress_create_schema_role | {role=regress_create_schema_role}
+ cs_setsess | regress_create_schema_role | {session_authorization=regress_create_schema_role}
+(2 rows)
+
+SET client_min_messages = warning;
+DROP SCHEMA regress_schema_setrole CASCADE;
+RESET client_min_messages;
+-- FINDING C08 (not new in d516974: the boundary is a9c350d, which made
+-- CREATE SCHEMA hand the new schema to its subcommands as a prepended
+-- search_path entry). Correct behavior: a schema named exactly "$user"
+-- receives the objects created by its own subcommands, like any other schema.
+-- Currently FAILS: "$user" in a search_path is a macro for the current role
+-- name, so the elements land in whatever the rest of the path resolves to,
+-- or, with an empty path, nowhere at all. Both halves run in an aborted
+-- transaction, so neither the buggy nor the fixed behavior can leave objects
+-- behind in another schema.
+BEGIN;
+CREATE SCHEMA "$user"
+ CREATE TABLE cs_dollar_tab (a int)
+ CREATE FUNCTION cs_dollar_func() RETURNS int LANGUAGE sql AS 'SELECT 1';
+SELECT relnamespace::regnamespace AS tab_schema FROM pg_class
+ WHERE relname = 'cs_dollar_tab';
+ tab_schema
+------------
+ "$user"
+(1 row)
+
+SELECT pronamespace::regnamespace AS func_schema FROM pg_proc
+ WHERE proname = 'cs_dollar_func';
+ func_schema
+-------------
+ "$user"
+(1 row)
+
+ROLLBACK;
+-- Again, with an empty search_path, where the statement today reports a
+-- search_path error for the very schema it just created.
+BEGIN;
+SET LOCAL search_path = '';
+CREATE SCHEMA "$user" CREATE TABLE cs_dollar_tab2 (a int);
+SELECT relnamespace::regnamespace AS tab2_schema FROM pg_class
+ WHERE relname = 'cs_dollar_tab2';
+ tab2_schema
+-------------
+ "$user"
+(1 row)
+
+ROLLBACK;
-- 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 526bb3c..cec7f77 100644
--- a/src/test/regress/sql/create_schema.sql
+++ b/src/test/regress/sql/create_schema.sql
@@ -177,5 +177,65 @@ CREATE SCHEMA regress_schema_misc
DROP SCHEMA regress_schema_misc CASCADE;
+--
+-- AUDIT ADDITIONS: user-visible defects of commit
+-- d516974840f4059d331ae6057ede3e4edd3c6747 ("Support more object types within
+-- CREATE SCHEMA."). Unlike every case above, each case below records the
+-- output that a CORRECT implementation would produce, so each one FAILS
+-- against master; the failure diff is the demonstration of the defect.
+--
+
+-- FINDING O1 (new in d516974). Correct behavior: a SET clause for "role" or
+-- "session_authorization" in a CREATE FUNCTION element behaves just as it
+-- does outside CREATE SCHEMA, so both functions below are created in the new
+-- schema, owned by the AUTHORIZATION role, with the setting in proconfig.
+-- Currently FAILS: CreateSchemaCommand runs the elements with
+-- InLocalUserIdChange() true, so guc.c's GUC_NOT_WHILE_SEC_REST test rejects
+-- the SET clause, naming a security-definer function that is not there. Two
+-- call paths reach that test and both need dealing with: CreateFunction ->
+-- GUCArrayAdd -> validate_option_array_item, which only validates
+-- (changeVal = false) and is what fails today, and then ProcedureCreate ->
+-- ProcessGUCArray, which applies proconfig around the language validator. A
+-- fix must not simply let the value through: SetSessionAuthorization()
+-- asserts that the security restriction context is clear.
+CREATE SCHEMA regress_schema_setrole AUTHORIZATION regress_create_schema_role
+ CREATE FUNCTION cs_setrole() RETURNS int LANGUAGE sql
+ SET role = 'regress_create_schema_role' AS 'SELECT 1'
+ CREATE FUNCTION cs_setsess() RETURNS int LANGUAGE sql
+ SET session_authorization = 'regress_create_schema_role' AS 'SELECT 2';
+SELECT p.proname, pg_get_userbyid(p.proowner) AS owner, p.proconfig
+ FROM pg_proc p JOIN pg_namespace n ON n.oid = p.pronamespace
+ WHERE n.nspname = 'regress_schema_setrole' ORDER BY 1;
+SET client_min_messages = warning;
+DROP SCHEMA regress_schema_setrole CASCADE;
+RESET client_min_messages;
+
+-- FINDING C08 (not new in d516974: the boundary is a9c350d, which made
+-- CREATE SCHEMA hand the new schema to its subcommands as a prepended
+-- search_path entry). Correct behavior: a schema named exactly "$user"
+-- receives the objects created by its own subcommands, like any other schema.
+-- Currently FAILS: "$user" in a search_path is a macro for the current role
+-- name, so the elements land in whatever the rest of the path resolves to,
+-- or, with an empty path, nowhere at all. Both halves run in an aborted
+-- transaction, so neither the buggy nor the fixed behavior can leave objects
+-- behind in another schema.
+BEGIN;
+CREATE SCHEMA "$user"
+ CREATE TABLE cs_dollar_tab (a int)
+ CREATE FUNCTION cs_dollar_func() RETURNS int LANGUAGE sql AS 'SELECT 1';
+SELECT relnamespace::regnamespace AS tab_schema FROM pg_class
+ WHERE relname = 'cs_dollar_tab';
+SELECT pronamespace::regnamespace AS func_schema FROM pg_proc
+ WHERE proname = 'cs_dollar_func';
+ROLLBACK;
+-- Again, with an empty search_path, where the statement today reports a
+-- search_path error for the very schema it just created.
+BEGIN;
+SET LOCAL search_path = '';
+CREATE SCHEMA "$user" CREATE TABLE cs_dollar_tab2 (a int);
+SELECT relnamespace::regnamespace AS tab2_schema FROM pg_class
+ WHERE relname = 'cs_dollar_tab2';
+ROLLBACK;
+
-- Clean up
DROP ROLE regress_create_schema_role;
Attachments:
[text/plain] d516974-a9c350d-create-schema-tests-v0.patch (120.7K, ../../20260904002924.a7.noahmisch@microsoft.com/2-d516974-a9c350d-create-schema-tests-v0.patch)
download | inline diff:
commit 8a98da0 (HEAD, cqla/create-schema-defect-tests)
Author: Noah Misch <noah@leadboat.com>
AuthorDate: Thu Sep 3 03:43:31 2026 +0000
Commit: Noah Misch <noah@leadboat.com>
CommitDate: Thu Sep 3 03:43:31 2026 +0000
Audit of d516974 (more object types in CREATE SCHEMA): report and tests
Automated, model-driven audit of commit d516974 ("Support more object types
within CREATE SCHEMA.") for user-visible defects still present in master
(6885b84). Everything here was written by a language model and has had no
human review; see PROVENANCE.md.
CREATE_SCHEMA_DEFECT_REPORT.md reports four defects attributable to d516974,
none severe:
D1 psql (and pgbench) do not end a CREATE SCHEMA at its semicolon when a
GRANT clause follows a CREATE FUNCTION/PROCEDURE clause: the routine
clause's BEGIN/END tracking state leaks into the GRANT, which never
resets it because GRANT is not introduced by CREATE. The statement is
merged with everything after it, so a following VACUUM fails with
"cannot run inside a transaction block" and later statements never run.
D2 A SET role / SET session_authorization clause on a routine element is
refused under AUTHORIZATION <other role>, with a message naming a
security-definer function that does not exist.
D3 PL/pgSQL cannot contain a CREATE SCHEMA whose element is a BEGIN ATOMIC
routine; pl_gram.y still uses psql's pre-049b742 heuristic.
D4 psql offers COLLATION and TYPE as CREATE SCHEMA clauses but then does not
complete them.
The report also has sections for findings belonging to the sibling commit
a9c350d rather than to d516974, for what was examined and found correct, and
for the 16 of 19 candidates that were investigated and rejected -- most because
the same behavior reproduces outside CREATE SCHEMA entirely, on builds that
predate the whole series.
There is no fix here, so the added tests encode what a correct implementation
would print and therefore FAIL on master by design; the failure diff is the
demonstration. Only test files are touched. With the additions, regress
(create_schema), plpgsql (plpgsql_misc), psql/001_basic subtests 135-137 and
psql/010_tab_completion subtests 110-111 fail, each only on the new material,
and three further meson tests fail collaterally because they run the core
parallel_schedule. Every added control passes. With the six test files
stashed, all six suites are green (243+13+134+101+2+5 subtests, 0 failures).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AfvwDcK3tFM4H1JeQXz2Jz
---
CREATE_SCHEMA_DEFECT_REPORT.md | 1203 ++++++++++++++++++++++++++
PROVENANCE.md | 305 +++++++
src/bin/psql/t/001_basic.pl | 40 +
src/bin/psql/t/010_tab_completion.pl | 95 ++
src/pl/plpgsql/src/expected/plpgsql_misc.out | 35 +
src/pl/plpgsql/src/sql/plpgsql_misc.sql | 31 +
src/test/regress/expected/create_schema.out | 78 ++
src/test/regress/sql/create_schema.sql | 60 ++
8 files changed, 1847 insertions(+)
diff --git a/CREATE_SCHEMA_DEFECT_REPORT.md b/CREATE_SCHEMA_DEFECT_REPORT.md
new file mode 100644
index 0000000..cabc4f3
--- /dev/null
+++ b/CREATE_SCHEMA_DEFECT_REPORT.md
@@ -0,0 +1,1203 @@
+# Audit of d516974 — "Support more object types within CREATE SCHEMA."
+
+## Verdict
+
+Four user-visible defects attributable to commit `d516974840f4059d331ae6057ede3e4edd3c6747`
+survive in master (`6885b845b4ba0b7aee09daa9817703477faa3704`), and none of them is severe.
+Two are in psql — its statement lexer merges a `CREATE SCHEMA` with everything after it when
+a `GRANT` clause follows a routine clause (D1), and its tab completion offers `COLLATION` and
+`TYPE` as `CREATE SCHEMA` clauses but then does not complete them (D4). One is in PL/pgSQL's
+own statement lexer, which cannot express a `CREATE SCHEMA` whose element is a `BEGIN ATOMIC`
+routine (D3). One is server-side: a `SET role` / `SET session_authorization` clause on a
+routine element is refused under `AUTHORIZATION` with a message naming a security-definer
+function that does not exist (D2).
+The server-side heart of the commit is in good shape: the newly-allowed sub-commands
+produce catalog contents, dependencies, ownership, event-trigger payloads, error positions
+and `pg_dump` output that match the same commands issued separately under an equivalent
+`search_path` — byte for byte, apart from pg_dump's per-dump `\restrict` nonce — and 16 hunt
+lenses plus a refute stage found no crash, no assertion failure, no privilege escalation and
+no data corruption *attributable* to the new syntax. (Two hazards of that kind are reachable
+*through* it and are reported below because they matter, but neither is caused by it: the
+confused deputy of §4 S2, and the dangling-`pronamespace` catalog corruption of §6 C06 —
+each reproduces on `pre` with no `CREATE SCHEMA` element list involved at all.)
+The most serious problems this audit found in the CREATE SCHEMA area — the `"$user"` schema
+that receives none of its own elements, the session temp schema outranking the new schema,
+and an element's user code hijacking the temporary `search_path` — all belong to the sibling
+commit **a9c350d**, not to d516974; they are reported in §4 as context. Sixteen of the nineteen
+hunt candidates were rejected outright, together with orchestrator findings O2 and O4 (§6) —
+most because the same behaviour reproduces outside `CREATE SCHEMA` entirely, on builds that
+predate the whole series.
+
+---
+
+## 1. Scope
+
+**Commit audited:** `d516974840f4059d331ae6057ede3e4edd3c6747`, Tom Lane, 2026-04-06,
+"Support more object types within CREATE SCHEMA." It adds `CreateDomainStmt`,
+`CreateFunctionStmt` and `DefineStmt` to the `schema_stmt` nonterminal
+(`src/backend/parser/gram.y:1686-1696`), adds `checkSchemaNameList()` and the new node cases
+in `transformCreateSchemaStmtElements()` (`src/backend/parser/parse_utilcmd.c`), tweaks
+psql tab completion and the psql lexer, and extends `create_schema.sgml`. Because the
+grammar's `DefineStmt` also produces `CompositeTypeStmt`, `CreateEnumStmt` and
+`CreateRangeStmt`, the newly-legal clauses are: `CREATE [OR REPLACE] FUNCTION`/`PROCEDURE`,
+`CREATE DOMAIN`, `CREATE [OR REPLACE] AGGREGATE` (modern and pre-8.2 syntax),
+`CREATE OPERATOR`, `CREATE TYPE` (shell, base, `AS (...)`, `AS ENUM`, `AS RANGE`),
+`CREATE COLLATION` (definition / `FROM` / `IF NOT EXISTS`) and
+`CREATE TEXT SEARCH PARSER | DICTIONARY | TEMPLATE | CONFIGURATION`.
+
+**Base commit:** `404db8f9edbb` (= `d516974^`).
+
+**Sibling commits, in the same series, NOT audited here:**
+`a9c350d9ee66` "Don't try to re-order the subcommands of CREATE SCHEMA." (= the commit that
+replaced per-element schema-name rewriting with a temporarily prepended `search_path`), and
+`404db8f9edbb` "Execute foreign key constraints in CREATE SCHEMA at the end."
+`a9c350d^` is `1ff3180ca016`. One later follow-up already in master:
+`049b742daad0` (2026-06-23) "psql: Tighten heuristics for BEGIN/END within CREATE SCHEMA."
+
+### What "defect" means in this report
+
+Every candidate was scored on three independent booleans, and a candidate is a defect iff
+`per_principles OR (sql_mismatch AND is_new)`:
+
+* **`sql_mismatch`** — the SQL standard requires behaviour master does not exhibit. Text
+ consulted: ISO/IEC 9075-2:200x committee draft at `/home/nm/src/pg/gbyrun/std/found.txt`.
+ No standard requirement is asserted in this report without a verbatim quotation.
+* **`per_principles`** — the commit's *own* principles require behaviour master does not
+ exhibit: its commit message, the code comments it wrote, the documentation it wrote, and
+ the plain proposition that a command `create_schema.sgml` lists as accepted inside
+ `CREATE SCHEMA` should behave inside `CREATE SCHEMA` the way it behaves outside, apart
+ from landing in the new schema. Crashes, assertion failures, wrong catalog contents,
+ silently-wrong results and misleading error messages are always violations.
+* **`is_new`** — the *rule violation* (not merely the syntax) is new relative to `d516974^`.
+ New syntax alone is not a defect.
+
+Attribution keys on the rule violation, not on which file the commit touched. A finding
+that reproduces on `old` with a pre-d516974-legal element, or outside `CREATE SCHEMA`
+entirely, is not a d516974 finding; those are in §4 and §6.
+
+### Reference builds
+
+| name | commit | meaning | server version |
+|---|---|---|---|
+| `new` | `6885b845b4ba` | master, **with** d516974 | 20devel |
+| `old` | `404db8f9edbb` | `d516974^`, with a9c350d and 404db8f | 19devel |
+| `pre` | `1ff3180ca016` | `a9c350d^`, before the whole series | 19devel |
+
+All three are meson builds with `cassert` and debug. `a9c350d` is an ancestor of `404db8f`;
+`d516974` is an ancestor of master; `git rev-list --count 404db8f..6885b84` = **1271**, so no
+`is_new` claim below rests on the new-vs-old behavioural delta alone — each is anchored in
+`git blame`/source or in a probe using pre-d516974 syntax.
+
+---
+
+## 2. Findings attributable to d516974
+
+Severity order. None is a crash, an assertion failure, a privilege escalation, or a source
+of catalog corruption; the cassert server logs are clean (`PANIC|FATAL|TRAP|Assert`) on every
+run cited in this report.
+
+---
+
+### D1 — psql (and pgbench) do not end a `CREATE SCHEMA` at its semicolon when a `GRANT` clause follows a routine clause
+
+**Severity: low-to-medium. New in d516974 (narrowed, not fixed, by 049b742).**
+
+**One-line statement.** A `CREATE [OR REPLACE] FUNCTION`/`PROCEDURE` clause arms
+`psqlscan.l`'s BEGIN/END counter; the per-clause reset that is supposed to disarm it fires
+only on the token `create`, and `GrantStmt` is the one `schema_stmt` alternative that does
+not begin with `CREATE` — so an unquoted identifier `begin` inside a following `GRANT`
+clause is counted as the start of a routine body, and psql sends the `CREATE SCHEMA` and
+everything after it as a single query.
+
+**Minimal reproduction** (build `new`; no role setup needed — the poisoning identifier is a
+view the same statement creates):
+
+```sql
+CREATE SCHEMA cs_gb
+ CREATE VIEW begin AS SELECT 1 AS one
+ CREATE FUNCTION cs_gb_f() RETURNS int LANGUAGE sql AS 'select 1'
+ GRANT SELECT ON begin TO public;
+VACUUM;
+SELECT 'split ok' AS result;
+```
+
+Run as `psql -X -q -A -t -v ON_ERROR_STOP=1 -f defect.sql`.
+
+**Literal observed output** (`/home/nm/src/pg/csxrun/evidence/CLIENT-C03-oracle-new.txt`):
+
+```
+############ CASE: defect ############
+psql:/tmp/tmp.vkud8hwplT/defect.sql:6: ERROR: VACUUM cannot run inside a transaction block
+psql exit=3
+-- schemas that exist now --
+
+```
+
+The blank line is the point: `cs_gb` does not exist. The script contains no transaction
+block. The server confirms the merge — with `log_statement=all` a *single*
+`LOG: statement:` entry carries all of the input
+(`/home/nm/src/pg/csxrun/evidence/ADJ-C03-A-new.txt`, `refute-C03-run2-allbuilds.txt`).
+
+**What a correct implementation produces, and why.** Changing nothing but the quoting of
+the same view name — `GRANT SELECT ON "begin" TO public` — is semantically identical SQL
+(`begin` is an unreserved keyword) and lexes correctly. Same file, same run:
+
+```
+############ CASE: oracle ############
+split ok
+psql exit=0
+-- schemas that exist now --
+cs_gb_oracle
+```
+
+so the oracle is master's own behaviour one quoting change away. The control — same shape
+with `CREATE VIEW` in place of the routine clause — also splits correctly (`CASE: control`
+in the same transcript, `cs_gc` created).
+
+**The worst symptom is in an interactive terminal, where there is no diagnostic at all.**
+Real pty, `--no-psqlrc`, `INPUTRC=/dev/null`
+(`/home/nm/src/pg/csxrun/evidence/CRIT-3-interactive-new.txt`, build `new`):
+
+```
+----- typed:
+CREATE SCHEMA cs1
+ CREATE FUNCTION f() RETURNS int LANGUAGE sql AS 'select 1'
+ GRANT ALL ON SCHEMA cs1 TO begin;
+SELECT 'MARKER_P' AS m;
+\echo ZZ_PROBE_END
+----- psql showed:
+CREATE SCHEMA cs1
+postgres-# CREATE FUNCTION f() RETURNS int LANGUAGE sql AS 'select 1'
+postgres-# GRANT ALL ON SCHEMA cs1 TO begin;
+postgres-# SELECT 'MARKER_P' AS m;
+postgres-# \echo ZZ_PROBE_END
+ZZ_PROBE_END
+postgres-#
+```
+
+The user typed a complete, semicolon-terminated statement; psql printed nothing, sent
+nothing to the server, and silently sat on the continuation prompt. Every later statement
+is swallowed the same way. The only exits are `\r` (which discards everything typed since
+the wedge) or `\g`. On `old` the identical keystrokes return the prompt to `postgres=#` and
+run the next statement (`CRIT-3-interactive-old.txt`). This consequence was found by the
+completeness critic and did **not** pass through the refute stage; it was not run on `pre`.
+
+**Mechanism** (against 6885b84).
+
+* `src/fe_utils/psqlscan.l:1102-1107` — the per-clause reset:
+ `if (pg_strcasecmp(identifier, "create") == 0) { state->sub_idents_count = 0; ... }`.
+* `src/backend/parser/gram.y:1686-1696` — `GrantStmt` is the only `schema_stmt` alternative
+ that does not begin with `CREATE`, so the reset never fires for a `GRANT` clause and
+ `sub_idents` still spells `c`,`f` from the preceding routine clause.
+* `src/fe_utils/psqlscan.l:1118-1123` — with `psqlscan_is_create_routine(state->sub_idents)`
+ still true, the identifier `begin` increments `begin_depth`.
+* `src/fe_utils/psqlscan.l:1099` — the whole per-clause block is skipped once
+ `begin_depth > 0`, so no later clause can recover. An unquoted `end` would decrement, but
+ `END` is reserved in a `GRANT` clause and a double-quoted `"end"` is not tracked at all.
+* The `;` rule then does not terminate the statement, and psql keeps accumulating.
+
+**Rubric scores.**
+
+* `sql_mismatch = false`. 11.1 `<schema definition>` (found.txt:25906-25953) contains no
+ statement terminator, and the only place the standard terminates a *directly invoked*
+ statement with a semicolon is 22.1 (`<direct SQL statement> ::= <directly executable
+ statement> <semicolon>`, found.txt:50046-50047) — which 4.25 immediately disclaims:
+ "In direct invocation of SQL, the method of invoking <direct SQL statement>s ... are
+ implementation-defined" (found.txt:4840-4844). A client's script-splitting heuristic is
+ exactly that implementation-defined method. No standard citation is offered for this
+ finding.
+* `per_principles = true`. Master's own comment at `src/fe_utils/psqlscan.l:1093-1095`
+ states the property that is violated: "In CREATE SCHEMA, track identifiers from each
+ top-level CREATE schema element separately, so that BEGIN/END tracking is enabled only
+ within CREATE [OR REPLACE] {FUNCTION|PROCEDURE} clauses." It is not. `049b742`'s commit
+ message repeats the promise — "only counting BEGIN/END within those clauses". (Both are
+ 049b742's text, not d516974's; d516974's own hedge — "a bit shaky but should be okay with
+ the present set of valid subcommands" — no longer exists in master.) d516974's own
+ documentation lists `CREATE FUNCTION`, `CREATE PROCEDURE` and `GRANT` together as accepted
+ clauses (`doc/src/sgml/ref/create_schema.sgml:104-120`) under a Description paragraph
+ promising that "The subcommands are treated essentially the same as separate commands
+ issued after creating the schema" (`create_schema.sgml:61-65`; that paragraph is
+ 3450fd08/2003, so it is corroboration of master's standing principle rather than
+ documentation this commit wrote). Consequences are two of the rubric's automatic
+ triggers: a silently-wrong result (psql prints `CREATE SCHEMA` for a schema that is then
+ rolled back) and a misleading error (`VACUUM cannot run inside a transaction block` for a
+ script containing no transaction block).
+* `is_new = true`. `csx-old/src/fe_utils/psqlscan.l` and `csx-pre/src/fe_utils/psqlscan.l`
+ are byte-identical (`diff -q`), and neither records the token `schema` at all, so
+ `begin_depth` could never rise inside a `CREATE SCHEMA` before d516974. Behaviourally,
+ on `old` the same input is terminated at its semicolon
+ (`/home/nm/src/pg/csxrun/evidence/ADJ-C03-A-old.txt` CASE 1: the server reports
+ `ERROR: syntax error at or near "FUNCTION"` for the `CREATE SCHEMA` alone and the
+ following statement runs). The arming prefix — `CREATE [OR REPLACE] {FUNCTION|PROCEDURE}`
+ as a schema clause — is exactly what d516974 legalized.
+
+**This is NOT the case 049b742 deferred** — the discriminator matters, because a reviewer
+will otherwise reject it as already-conceded. 049b742's message defers
+`CREATE FUNCTION begin () ...`, "true all along with no field complaints". Measured on
+master (`/home/nm/src/pg/csxrun/evidence/XPREM-p6-new.txt`):
+
+| case | shape | result |
+|---|---|---|
+| K1 | `CREATE SCHEMA k1 CREATE FUNCTION begin () ...` | merged; `k1 schema exists: 0` |
+| K1b | the same routine **at top level** | merged too — the deferred family fails identically outside |
+| K2 | this finding: unremarkable `CREATE FUNCTION k2f ()`, `begin` in the following `GRANT` | merged; `k2 schema exists: 0` |
+| K2b | the identical routine **and** the identical `GRANT` at top level | **works**: `CREATE FUNCTION` / `GRANT` / `VACUUM` / `K2B_MARKER` |
+| K3 | pre-v19-legal `CREATE TABLE` clause + `GRANT ... TO begin` | **works**; `k3 schema exists: 1` |
+
+K1/K1b are the conceded family; the top-level heuristic is fooled and CREATE SCHEMA
+inherits it. K2/K2b are not: the top-level heuristic is *not* fooled by these tokens, and
+the leak is across the clause boundary — precisely what 049b742 promised to prevent. K3
+confirms 049b742's other claim, that nothing that worked before v19 was put at risk.
+
+**Honest narrowing.** The damage *class* is neither new nor CREATE SCHEMA-specific: a plain
+top-level `CREATE FUNCTION x1f() RETURNS int LANGUAGE sql SET search_path = begin AS 'select 1';`
+swallows a following `VACUUM` identically on master and on `old`
+(`XPREM-p3-new.txt`, `XPREM-p3-old.txt`). What is new is the leak across a clause boundary.
+The trigger also needs an object named with the unreserved keyword `begin` referenced
+unquoted in a `GRANT` clause after a routine clause.
+
+**Attribution: d516974** (introduced), narrowed by 049b742.
+
+**Other consequences verified on master, from the same merge:** pgbench (which links the same
+`psql_scan()`) reports one command instead of three in simple mode
+(`refute-C03-run3.txt` X4); `\i` runs the included file *before* the textually preceding
+`CREATE SCHEMA` (`refute-C03-run3.txt` X3, `H13-psqlmodes-new-1.txt` P5); the poisoning
+identifier can arrive via `:var` interpolation (`H13-psqlmodes-new-1.txt` P8), so a script
+whose text never contains the word `begin` can be poisoned from outside it — with
+`psql -X -v r=begin -f`, a file containing only `GRANT USAGE ON SCHEMA v1 TO :r;` in the
+`GRANT` clause merges and the schema is rolled back, while `-v r=public` splits correctly
+(`FACTCHECK-D1-var-new.txt`).
+
+**Test on this branch: yes.** `src/bin/psql/t/001_basic.pl`, subtests 135-137 (of 140),
+added at lines 580-618 with a passing control at subtests 138-140. Verbatim failure
+(`/home/nm/src/pg/csxrun/evidence/FINAL-tap.txt`):
+
+```
+not ok 135 - CREATE SCHEMA ends at its semicolon: exit code 0
+# got: '3'
+not ok 136 - CREATE SCHEMA ends at its semicolon: no stderr
+# got: 'psql:<stdin>:6: ERROR: VACUUM cannot run inside a transaction block'
+not ok 137 - CREATE SCHEMA ends at its semicolon: matches
+# ''
+```
+
+The expected values encode correct behaviour, so the test fails on master by design.
+
+---
+
+### D2 — a `SET role` / `SET session_authorization` clause on a routine element is refused under `AUTHORIZATION`, naming a security-definer function that does not exist
+
+**Severity: low. Reachability is new in d516974; the rule violation is reachable on `pre` by
+a harder route (see Attribution).**
+
+**One-line statement.** Inside `CREATE SCHEMA ... AUTHORIZATION <role other than the current
+user>`, every `SET`/`RESET` clause on a `CREATE [OR REPLACE] FUNCTION`/`PROCEDURE` element
+that names `role` or `session_authorization` is rejected — including when the invoker is a
+superuser and the `AUTHORIZATION` role is a superuser — with a message that is false.
+
+**Minimal reproduction** (build `new`, as the bootstrap superuser):
+
+```sql
+CREATE ROLE regress_plan_super SUPERUSER;
+CREATE SCHEMA sa1 AUTHORIZATION regress_plan_super
+ CREATE FUNCTION f1() RETURNS int LANGUAGE sql SET role = 'regress_plan_super' AS 'select 1';
+```
+
+**Literal observed output** (`/home/nm/src/pg/csxrun/evidence/PLAN-o1o2-new.txt`):
+
+```
+CREATE SCHEMA sa1 AUTHORIZATION regress_plan_super
+ CREATE FUNCTION f1() RETURNS int LANGUAGE sql SET role = 'regress_plan_super' AS 'select 1';
+psql:/home/nm/src/pg/csxrun/probe/plan/o1o2.sql:6: ERROR: cannot set parameter "role" within security-definer function
+```
+
+Controls in the same transcript: the identical `CREATE FUNCTION` **at top level** succeeds
+(`O1d`: `CREATE FUNCTION`); the identical element inside `CREATE SCHEMA` **without**
+`AUTHORIZATION` succeeds (`O1c`: `CREATE SCHEMA`); `AUTHORIZATION CURRENT_USER`, where
+`saved_uid == owner_uid` so `SetUserIdAndSecContext` is skipped, succeeds (`O1f`); a plain
+non-role `SET` clause under `AUTHORIZATION` succeeds (`O1g`). Every spelling that names
+either GUC fails — twelve were enumerated and all twelve error
+(`/home/nm/src/pg/csxrun/evidence/FACTCHECK-D2-spellings-new.txt`): `SET role =`,
+`SET role TO`, `SET role TO DEFAULT`, `SET role FROM CURRENT`, `RESET role`, the four
+`session_authorization` analogues, and `SET SESSION AUTHORIZATION <literal>`,
+`SET SESSION AUTHORIZATION DEFAULT`, `RESET SESSION AUTHORIZATION`. `RESET ALL` alone slips
+through (`/home/nm/src/pg/csxrun/evidence/h02-a-new.txt` A1, verbatim):
+
+```
+CREATE SCHEMA a1c AUTHORIZATION regress_bob CREATE FUNCTION f() RETURNS int LANGUAGE sql AS 'select 1' RESET role;
+psql:/home/nm/src/pg/csxrun/probe/h02-a.sql:15: ERROR: cannot set parameter "role" within security-definer function
+CREATE SCHEMA a1d AUTHORIZATION regress_bob CREATE FUNCTION f() RETURNS int LANGUAGE sql AS 'select 1' RESET session_authorization;
+psql:/home/nm/src/pg/csxrun/probe/h02-a.sql:16: ERROR: cannot set parameter "session_authorization" within security-definer function
+CREATE SCHEMA a1e AUTHORIZATION regress_bob CREATE FUNCTION f() RETURNS int LANGUAGE sql AS 'select 1' RESET ALL;
+CREATE SCHEMA
+```
+
+Note `RESET role`: nothing is being *set* at all, yet the message says "cannot set
+parameter".
+
+**What a correct implementation produces, and why.** The documented equivalent —
+`SET ROLE r; CREATE FUNCTION r_schema.f() ... SET role = 'r';` — succeeds and stores
+`proconfig = {role=r}`, and so does the same `CREATE SCHEMA` when `AUTHORIZATION` names the
+current role. Verbatim oracle from master
+(`/home/nm/src/pg/csxrun/evidence/PLAN-oracle-new.txt`, `AUTHORIZATION CURRENT_ROLE` under
+`SET ROLE`):
+
+```
+ proname | nspname | owner | proconfig
+------------+------------------------+----------------------------+----------------------------------------------------
+ cs_setrole | regress_schema_setrole | regress_create_schema_role | {role=regress_create_schema_role}
+ cs_setsess | regress_schema_setrole | regress_create_schema_role | {session_authorization=regress_create_schema_role}
+(2 rows)
+```
+
+**Mechanism** (against 6885b84).
+
+* `src/backend/commands/schemacmds.c:149-150` —
+ `SetUserIdAndSecContext(owner_uid, save_sec_context | SECURITY_LOCAL_USERID_CHANGE)` for
+ the whole element body, so `InLocalUserIdChange()` is true (and
+ `InSecurityRestrictedOperation()` is not).
+* `src/backend/utils/misc/guc.c:3541-3553` — the `GUC_NOT_WHILE_SEC_REST` block tests
+ `InLocalUserIdChange()` *before* consulting `changeVal`, so a validation-only call is
+ rejected. The comment at `guc.c:3546-3548` itself calls the phrasing "historical, but
+ it's the most common case".
+* Two call paths reach it and a fix must handle both — established by a
+ `backtrace_functions` trace during the verify pass: first
+ `CreateFunction -> GUCArrayAdd -> validate_option_array_item -> set_config_option(..., changeVal = false, ...)`,
+ which is what fails today; then `ProcedureCreate -> ProcessGUCArray(..., GUC_ACTION_SAVE)`
+ with `changeVal = true`, which applies proconfig around the language validator (gated on
+ `check_function_bodies`, `pg_proc.c:742-755`). The verify pass reports that gating the
+ guard on `changeVal` alone still fails, because the second site then hits it; what is
+ re-verified here is that `check_function_bodies = off` does **not** avoid the error,
+ because the first site still fires
+ (`/home/nm/src/pg/csxrun/evidence/VERIFY-o1-mechanism-new.txt`). A fix must also stop
+ short of actually applying the value: a throwaway patch that
+ simply dropped the guard PANICked the server on
+ `Assert(SecurityRestrictionContext == 0)` — `miscinit.c:491`, in `SetOuterUserId()`,
+ which the `role` GUC's assign path reaches via `SetCurrentRoleId()` (`miscinit.c:981`);
+ `SetSessionUserId()` carries the same assertion at `miscinit.c:525`. (That throwaway
+ tree was deleted after the verify pass, so this one item is reported, not re-run, by the
+ fact-check; the two assertions and both call paths are verified in master's source.)
+* Only `role` and `session_authorization` carry `GUC_NOT_WHILE_SEC_REST`;
+ `functioncmds.c:683-684` short-circuits `VAR_RESET_ALL`, which is why `RESET ALL` escapes.
+
+**Rubric scores.**
+
+* `sql_mismatch = false`. `proconfig` SET clauses have no counterpart in ISO/IEC 9075, and
+ 11.1's Access Rules make the privilege environment implementation-defined. No citation is
+ offered.
+* `per_principles = true`, on two independent grounds. (a) The rubric's automatic trigger:
+ the message names a security-definer function, and there is none anywhere — at top level a
+ bad value instead gives the informative `NOTICE: role "..." does not exist` +
+ `ERROR: role "..." does not exist` (`h02-e-new.txt` E5), so the false message pre-empts the
+ accurate one. (b) d516974 added `CREATE FUNCTION` and `CREATE PROCEDURE` to the
+ accepted-clause list it wrote at `create_schema.sgml:104-120`, sitting under a Description
+ paragraph whose only stated exception is ownership.
+* `is_new`: **true for this spelling, false for the rule.** On `old` and `pre` the statement
+ is `ERROR: syntax error at or near "FUNCTION"`. But on `pre`, using only pre-d516974-legal
+ elements, the identical false message already appears in the identical environment
+ (`/home/nm/src/pg/csxrun/evidence/adjC12-a3-pre.txt`, verbatim):
+
+ ```
+ CREATE SCHEMA sq AUTHORIZATION regress_bob
+ CREATE TABLE parent (a int) PARTITION BY RANGE (a)
+ CREATE TABLE child PARTITION OF parent FOR VALUES FROM (public.fx(1)) TO (public.fx(10));
+ psql:/home/nm/src/pg/csxrun/probe/adjC12/a3.sql:9: ERROR: cannot set parameter "role" within security-definer function
+ ```
+
+ where `public.fx` merely carries `proconfig = {role=regress_bob}`; the documented
+ separate-command form in the same transcript succeeds. Because `per_principles` is true,
+ the verdict does not depend on `is_new`.
+
+**Attribution: shared.** d516974 owns the reachability (one statement, no setup, from a
+clause it added and documented) and the specific *validation-only* instance; the guc.c and
+schemacmds.c code on the failing path is untouched by d516974 —
+`git show --stat d516974` modifies no file under `src/backend/commands/` or
+`src/backend/utils/`. A reviewer who keys attribution strictly on the rule violation should
+read this section together with §6's rejected candidate C12 and file it as pre-existing,
+newly exposed. Either way the fix belongs in `guc.c` (consult `changeVal`, fix the wording)
+or `schemacmds.c` (push a GUC nest level so the userid change need not be "local"), never in
+anything d516974 wrote.
+
+**Also reachable from a `CREATE EXTENSION` script** — superuser-installed and non-superuser
+trusted alike (`h02-f-new.txt` F2/F3/F7), where it blocks the natural way to write an
+extension that ships a routine with a `SET role` clause into an `AUTHORIZATION`-owned schema.
+
+**Test on this branch: yes.** `src/test/regress/{sql,expected}/create_schema.{sql,out}`,
+block at `sql/create_schema.sql:188-211`; failure hunk `@@ -347,17 +347,17 @@` of
+`testrun/regress/regress/regression.diffs`
+(`/home/nm/src/pg/csxrun/evidence/FINAL-regressdiffs.txt`):
+
+```
++ERROR: cannot set parameter "role" within security-definer function
+ SELECT p.proname, pg_get_userbyid(p.proowner) AS owner, p.proconfig
+ FROM pg_proc p JOIN pg_namespace n ON n.oid = p.pronamespace
+ WHERE n.nspname = 'regress_schema_setrole' ORDER BY 1;
+- proname | owner | proconfig
+-------------+----------------------------+----------------------------------------------------
+- cs_setrole | regress_create_schema_role | {role=regress_create_schema_role}
+- cs_setsess | regress_create_schema_role | {session_authorization=regress_create_schema_role}
+-(2 rows)
++ proname | owner | proconfig
++---------+-------+-----------
++(0 rows)
+
+ SET client_min_messages = warning;
+ DROP SCHEMA regress_schema_setrole CASCADE;
++ERROR: schema "regress_schema_setrole" does not exist
+```
+
+---
+
+### D3 — PL/pgSQL cannot contain a `CREATE SCHEMA` whose element is a `BEGIN ATOMIC` routine
+
+**Severity: low. New in d516974.**
+
+**One-line statement.** `pl_gram.y`'s copy of psql's statement-boundary heuristic recognizes
+`BEGIN`/`END` only after a leading `CREATE [OR REPLACE] {FUNCTION|PROCEDURE}`, never after
+`CREATE SCHEMA`, so it cuts the statement at the first semicolon inside the routine body —
+making a construct that `create_schema.sgml` lists as accepted unwritable in PL/pgSQL.
+
+**Minimal reproduction** (build `new`):
+
+```sql
+DO $$ BEGIN CREATE SCHEMA a_s CREATE FUNCTION a_f() RETURNS int BEGIN ATOMIC SELECT 1; END; END $$;
+```
+
+**Literal observed output** (`/home/nm/src/pg/csxrun/evidence/adjC14-new-1.txt`, case A):
+
+```
+DO $$ BEGIN CREATE SCHEMA a_s CREATE FUNCTION a_f() RETURNS int BEGIN ATOMIC SELECT 1; END; END $$;
+psql:/home/nm/src/pg/csxrun/probe/adjC14/adj.sql:2: ERROR: syntax error at end of input
+LINE 1: ...E FUNCTION a_f() RETURNS int BEGIN ATOMIC SELECT 1; END; END...
+ ^
+```
+
+Controls in the same transcript: the same routine in PL/pgSQL but **not** inside
+`CREATE SCHEMA` returns `DO` (case B); the same `CREATE SCHEMA` submitted directly returns
+`CREATE SCHEMA` (case C); the same statement via plpgsql `EXECUTE` returns `DO` (case D); an
+`EXCEPTION` handler does not catch it, because it is a compile-time failure (case G); and a
+named plpgsql function containing it fails at `CREATE FUNCTION` time (case H). The trigger
+is exactly one or more statements in the `ATOMIC` body — an empty body works (case F).
+
+**What a correct implementation produces, and why.** The byte-identical `CREATE SCHEMA`
+submitted at top level on master succeeds
+(`/home/nm/src/pg/csxrun/evidence/PLAN-oracle4-new.txt`):
+
+```
+create schema misc_schema
+ create function test3() returns int
+ begin atomic
+ select 3 + 3;
+ end;
+CREATE SCHEMA
+CREATE OR REPLACE FUNCTION misc_schema.test3()
+ RETURNS integer
+ LANGUAGE sql
+BEGIN ATOMIC
+ SELECT (3 + 3);
+END
+```
+
+so the correct output is a real oracle, not a guess.
+
+**Mechanism** (against 6885b84). `src/pl/plpgsql/src/pl_gram.y:3032` `make_execsql_stmt()`
+keeps `char tokens[4]` (`:3049`) and updates it only while
+`tokens[0] == 'c' && token_count < sizeof(tokens)` (`:3104-3105`). For
+`CREATE SCHEMA s CREATE FUNCTION ...` the trace is: `CREATE` → `tokens[0]='c'`;
+`SCHEMA` → nothing (`schema` is a PL/pgSQL *unreserved keyword*,
+`src/pl/plpgsql/src/pl_unreserved_kwlist.h:101`, so `plpgsql_yylex` returns `K_SCHEMA` and
+none of the `tok == T_WORD && strcmp(...)` tests at `:3107-3116` can fire); then the name;
+then the second `CREATE`; then the block is skipped forever. `in_routine_definition`
+(`:3047`) stays false, `begin_depth` stays 0, and the `;` after `SELECT 1` breaks the scan at
+`:3136-3137`. The comment at `:3065-3066` still says "We follow psql's lead in not
+recognizing BEGIN/END except after CREATE [OR REPLACE] {FUNCTION|PROCEDURE}" — a statement of
+intent that this very series made false, since psql's lead moved in d516974 + 049b742 and
+`pl_gram.y` was not updated by any commit in the series.
+
+Structurally this can never be *silent*: `tokens[1]` is the token after `CREATE`, which for
+`CREATE SCHEMA` is always `SCHEMA`, so `in_routine_definition` can never be spuriously true
+and truncation always cuts a routine body before its `END` (verified with a leading
+`CREATE TABLE` clause that would leave a valid prefix — `refC14-r2-new.txt` R2-C still
+errors, nothing created).
+
+**Rubric scores.**
+
+* `sql_mismatch = false`, deliberately. `grep -ci 'plpgsql\|PL/pgSQL' found.txt` = 0;
+ 11.1's Syntax Rules constrain what contained elements may name, and nothing in the standard
+ speaks to how a vendor's procedural language finds the end of an embedded statement. The
+ server accepts the SQL — cases C and D prove it.
+* `per_principles = true`. d516974's own documentation lists `CREATE FUNCTION` and
+ `CREATE PROCEDURE` as accepted clauses (`create_schema.sgml:104-120`), and
+ `doc/src/sgml/plpgsql.sgml` still says without qualification that "In general, any SQL
+ command that does not return rows can be executed within a PL/pgSQL function just by
+ writing the command." Case B proves the identical routine written as a separate command in
+ the same `DO` block works, so the documented equivalence fails only for the
+ in-`CREATE SCHEMA` form. (The "misleading error message" argument is weaker than it looks
+ and is *not* relied on: in the realistic multi-line form the display is
+ `LINE 7: select 3 + 3;` with the caret at end of line and nothing after it —
+ `refC14-r1-new.txt` R1-F, `XPREM-p5-new.txt` — and running the truncated fragment alone
+ produces the identical message legitimately, `refC14-r2-new.txt` R2-A.)
+* `is_new = true`, by reachability rather than by file ownership. On `old` and `pre` the
+ same `DO` block fails with `ERROR: syntax error at or near "FUNCTION"`
+ (`adjC14-old-1.txt`, `adjC14-pre-1.txt`, byte-identical) — the backend grammar rejects the
+ element outright, so no documented-legal element was being refused and there was no rule to
+ violate. Every pre-d516974-legal element shape with a depth-0 hazard passes on `old`:
+ semicolon inside a string literal, `CASE ... END` in a view, four chained elements
+ including `GRANT`, and 049b742's `CREATE VIEW begin` case — all return `DO`, `q1_ok..q4_ok = 1`
+ (`/home/nm/src/pg/csxrun/evidence/XPREM-p2-old.txt`).
+
+**Attribution: d516974.** The heuristic in `pl_gram.y` is `57b440ec` (2024) and was not
+touched by the series; what d516974 supplies is the documented-legal construct that the
+heuristic cannot parse. Note for a fixer: PL/pgSQL is *immune* to the hazard 049b742 fixed
+(`DO $$ BEGIN CREATE SCHEMA m5 CREATE VIEW begin AS SELECT 1; END $$;` returns `DO`,
+`refC14-r4-new.txt` R4-5) precisely because `in_routine_definition` is never set — so a naive
+"count BEGIN/END throughout CREATE SCHEMA" port would import that regression. `pl_gram.y`'s
+other known semicolon hazard, `CREATE RULE` with multiple actions, is already covered by its
+`paren_depth` counter and works in a `DO` block on all three builds
+(`refC14-r1-new.txt` R1-D, `refC14-r3-oldpre.txt` R3-B), so this is the sole outstanding gap
+in that heuristic.
+
+**Test on this branch: yes.** `src/pl/plpgsql/src/{sql,expected}/plpgsql_misc.{sql,out}`,
+block at `sql/plpgsql_misc.sql:24-53`, sitting beside the `57b440e` test it mirrors. Single
+failure hunk `@@ -55,15 +55,13 @@` (`/home/nm/src/pg/csxrun/evidence/FINAL-regressdiffs.txt`):
+
+```
++ERROR: syntax error at end of input
++LINE 7: select 3 + 3;
++ ^
+ \sf misc_schema.test3
+-CREATE OR REPLACE FUNCTION misc_schema.test3()
+- RETURNS integer
+- LANGUAGE sql
+-BEGIN ATOMIC
+- SELECT (3 + 3);
+-END
++ERROR: function "misc_schema.test3" does not exist
+ drop schema misc_schema cascade;
+-NOTICE: drop cascades to function misc_schema.test3()
++ERROR: schema "misc_schema" does not exist
+```
+
+---
+
+### D4 — psql offers `COLLATION` and `TYPE` as `CREATE SCHEMA` clauses but does not complete them
+
+**Severity: cosmetic. This is the weakest item in this report; the counterarguments are
+stated in full below.**
+
+**One-line statement.** d516974 added `COLLATION` and `TYPE` to the clause keywords psql
+offers after `CREATE SCHEMA ... CREATE`, and in the same diff converted two other clause
+families (`CREATE DOMAIN`, `CREATE TEXT SEARCH`) from anchored `Matches()` to `TailMatches()`
+"so use TailMatches" — but left `CREATE COLLATION`'s and `CREATE TYPE`'s own rules anchored,
+so those two complete at top level and not inside `CREATE SCHEMA`.
+
+**Minimal reproduction** — interactive psql with readline; the audit used
+`src/bin/psql/t/010_tab_completion.pl`'s own pty harness. Type
+`CREATE SCHEMA s CREATE COLLATION c FR<TAB>` and `CREATE SCHEMA s CREATE TYPE t AS EN<TAB>`.
+
+**Literal observed output** (`/home/nm/src/pg/csxrun/evidence/FINAL-tap.txt`):
+
+```
+not ok 110 - complete CREATE COLLATION inside CREATE SCHEMA
+# Actual output was "CREATE SCHEMA s CREATE COLLATION c FR\r\rpostgres=# \r\rpostgres=# \\r\r\nQuery buffer reset (cleared).\r\npostgres=# "
+# Did not match "(?^:CREATE COLLATION c FROM )"
+not ok 111 - complete CREATE TYPE inside CREATE SCHEMA
+# Actual output was "CREATE SCHEMA s CREATE TYPE t AS EN\r\rpostgres=# \r\rpostgres=# \\r\r\nQuery buffer reset (cleared).\r\npostgres=# "
+# Did not match "(?^:CREATE TYPE t AS ENUM )"
+```
+
+Nothing is offered: the tab is inert.
+
+**What a correct implementation produces, and why.** The byte-identical tails complete at
+top level, and the two families d516974 *did* convert complete inside `CREATE SCHEMA`. All
+four are passing subtests in the same run: `ok 102 - complete CREATE DOMAIN inside CREATE
+SCHEMA`, `ok 104 - complete CREATE TEXT SEARCH inside CREATE SCHEMA`,
+`ok 112 - complete CREATE COLLATION at top level`,
+`ok 114 - complete CREATE TYPE at top level`; plus
+`ok 106`/`ok 108`, which show psql offering `COLLATION` and `TYPE` as clauses in the first
+place (`/home/nm/src/pg/csxrun/evidence/CLIENT-O3-tap-new.txt`).
+
+**Mechanism** (against 6885b84).
+
+* `src/bin/psql/tab-complete.in.c:2199-2207` — the clause list, rewritten by d516974, now
+ offers `"AGGREGATE", "COLLATION", "DOMAIN", "FUNCTION", "INDEX", "OPERATOR", "PROCEDURE",
+ "SEQUENCE", "TABLE", "TEXT SEARCH ...", "TRIGGER", "TYPE", "VIEW"` (plus `"UNIQUE"` and
+ `"UNLOGGED"`, for `INDEX` and `TABLE`/`SEQUENCE` respectively).
+* `tab-complete.in.c:3521` — `/* CREATE DOMAIN --- is allowed inside CREATE SCHEMA, so use
+ TailMatches */`, and `:3893` the same for `CREATE TEXT SEARCH`: both converted by d516974.
+* `tab-complete.in.c:3493`, `:3495` — `Matches("CREATE", "COLLATION", MatchAny)` and
+ `Matches("CREATE", "COLLATION", MatchAny, "FROM")`: still anchored.
+* `tab-complete.in.c:4163`, `:4165`, `:4174` — `Matches("CREATE", "TYPE", ...)`: still
+ anchored. `Matches()` requires `previous_words_count == narg`, which cannot hold once
+ `CREATE SCHEMA s` precedes.
+
+**Correction to the orchestrator's original statement of this finding.** It named
+`CREATE FUNCTION`, `CREATE PROCEDURE`, `CREATE AGGREGATE` and `CREATE OPERATOR` as also
+affected. They are not: an exhaustive grep of master's `tab-complete.in.c` for
+`(Matches|TailMatches|HeadMatches)("CREATE", "FUNCTION|PROCEDURE|AGGREGATE|OPERATOR"` returns
+nothing — psql has no completion rules for those four object types anywhere, inside or
+outside `CREATE SCHEMA`. The defect is exactly `CREATE COLLATION` (3 rules) and
+`CREATE TYPE` (6 rules). Of the nine, four (`:3497`, `:4167`, `:4176`, `:4190`) cannot be
+rewritten as `TailMatches` at all — they anchor the head of an open parenthesised option
+list, and psql's matcher vocabulary has no tail-anchored-head form. The actionable omission
+is five one-word edits (`:3493`, `:3495`, `:4163`, `:4165`, `:4174`).
+
+**Rubric scores.**
+
+* `sql_mismatch = false` — and for the right reason. Collations and user-defined types *are*
+ `<schema element>`s in the standard (found.txt:25931-25953), but ISO/IEC 9075 has no
+ requirements about a client's interactive completion. On the server axis d516974 made
+ PostgreSQL strictly more conformant.
+* `per_principles = true`, narrowly: the commit wrote the rule into the tree in its own diff
+ ("--- is allowed inside CREATE SCHEMA, so use TailMatches"), added `COLLATION` and `TYPE` to
+ the list of clauses it advertises, and did not apply the rule to them. That is an
+ internal-coherence gap in one commit's own diff.
+* `is_new = true` for the two families, but see the counterarguments.
+
+**Counterarguments a reviewer should weigh — all of them verified, none of them refuted.**
+
+1. The invariant "everything psql offers as a clause completes inside `CREATE SCHEMA`" was
+ *never* universal. `CREATE TRIGGER` — one of the five clause keywords psql offered before
+ d516974 — goes dead inside `CREATE SCHEMA` from `ON <table>` onward, on `old`, `pre` and
+ master alike. Measured on `old` (`/home/nm/src/pg/csxrun/evidence/refute-C18-old-1.txt`):
+ `[CREATE SCHEMA s CREATE TRIGGER trg AFTER INSERT ON tab1 ]` + TAB TAB offers nothing,
+ while the same tail at top level offers
+ `DEFERRABLE FOR NOT DEFERRABLE WHEN ( EXECUTE FUNCTION INITIALLY REFERENCING`.
+2. Un-anchoring is a documented, complaint-driven backlog. `tab-complete.in.c:20-22` says
+ the file "does not always give you all the syntactically legal completions"; commit
+ `9b181b0`, where head-anchoring became the default and where the very "so use TailMatches"
+ comment was written, says "we can put those back when we get complaints"; and `ef0938f`
+ (2024) *added* head-anchored `CREATE TRIGGER` rules under that comment.
+3. The mechanism is generic, not CREATE SCHEMA-specific: `[BEGIN; CREATE TYPE t ]` + TAB is
+ equally dead, and `[BEGIN; CREATE COLLATION c FROM ]` + TAB offers
+ `information_schema. public. mytab123 tab1` — on `new`, `old` and `pre` alike
+ (`/home/nm/src/pg/csxrun/evidence/refute-C18-new-1.txt` lines 120-186). The
+ `COLLATION ... FROM` case offering non-collation names is therefore a pre-existing psql
+ behaviour and is **not** charged to d516974.
+
+**Attribution: d516974**, for the two families it advertised and did not convert. Cosmetic:
+no crash, no wrong catalog contents, no silently-wrong result; a user who accepts a wrong
+suggestion gets an immediate, accurate server error.
+
+**Test on this branch: yes.** `src/bin/psql/t/010_tab_completion.pl`, subtests 110-111 (of
+115), body at lines 486-556, with six passing controls around them (102, 104, 106, 108, 112,
+114). A new helper `check_completion_nofail()` at lines 129-150 is *required*, not cosmetic:
+the file's own `check_completion()` waits `PG_TEST_TIMEOUT_DEFAULT` and then **dies**, which
+would abort the file after two subtests
+(`/home/nm/src/pg/csxrun/evidence/PLAN-o3-die-new.txt`).
+
+---
+
+## 3. Complete failure set produced by this branch's tests
+
+The four tests above make **seven** meson tests fail, not four. Three of the seven are
+collateral: `pg_upgrade/002_pg_upgrade`, `recovery/027_stream_regress` and
+`test_plan_advice/001_replan_regress` each run the core `parallel_schedule`, so each fails
+only on the shared `create_schema` test with a `regression.diffs` whose body is byte-identical
+to `regress/regress`'s. That is inherent to any regress-file test encoding correct behaviour.
+
+From `/home/nm/src/pg/csxrun/evidence/FINAL-suiterun.txt`:
+
+```
+1/9 postgresql:psql / psql/020_cancel OK 1.32s 2 subtests passed
+2/9 postgresql:psql / psql/030_pager OK 1.54s 5 subtests passed
+3/9 postgresql:psql / psql/010_tab_completion ERROR 1.64s exit status 2
+4/9 postgresql:plpgsql / plpgsql/regress ERROR 3.98s exit status 1
+5/9 postgresql:psql / psql/001_basic ERROR 8.11s exit status 3
+6/9 postgresql:regress / regress/regress ERROR 87.69s exit status 1
+7/9 postgresql:test_plan_advice / test_plan_advice/001_replan_regress ERROR 90.62s exit status 1
+8/9 postgresql:recovery / recovery/027_stream_regress ERROR 95.70s exit status 1
+9/9 postgresql:pg_upgrade / pg_upgrade/002_pg_upgrade ERROR 99.04s exit status 1
+Ok: 2
+Expected Fail: 0
+Fail: 7
+```
+
+| meson test | failing unit | finding |
+|---|---|---|
+| `regress/regress` | `create_schema`, 1 of 243 | D2, and S1 below |
+| `plpgsql/regress` | `plpgsql_misc`, 1 of 13 | D3 |
+| `psql/001_basic` | subtests 135-137 of 140 | D1 |
+| `psql/010_tab_completion` | subtests 110-111 of 115 | D4 |
+| `pg_upgrade/002_pg_upgrade` | subtest 5 "regression tests in old instance" | collateral |
+| `recovery/027_stream_regress` | subtest 2 "regression tests pass" | collateral |
+| `test_plan_advice/001_replan_regress` | subtest 1 "regression tests pass" | collateral |
+
+`pg_upgrade` subtest 20 ("old and new dumps match after pg_upgrade") and `recovery` subtests 7
+and 10 still pass, proving the added blocks leave no catalog residue. With the additions
+stashed, all seven are green. Four of the five shipped tests were shown to flip to passing
+under a throwaway fix (D1 by resetting sub-clause state on `grant`; D3 by porting psqlscan.l's
+per-clause tracking to `pl_gram.y`; D4 by two `Matches` → `TailMatches` edits; D2 by a guc.c
+flag, which removes the D2 hunk and leaves only S1's two); S1 was not fix-checked because a
+fix is a redesign.
+
+---
+
+## 4. Findings that belong to the sibling commit a9c350d, not to d516974
+
+These are presented as **context**. Each is a real, user-visible defect in master, and one
+of them (S2) is the most severe thing this audit found anywhere — but each reproduces on
+`old` (= `d516974^`) with a pre-d516974-legal element, so d516974 did not cause it. Nothing
+was found attributable to `404db8f`.
+
+The common cause: `a9c350d` deleted `setSchemaName()`, which used to write the new schema's
+name into each element's `RangeVar->schemaname`
+(`/home/nm/src/pg/csx-pre/src/backend/parser/parse_utilcmd.c:4521-4535`, called at
+`:4450/4459/4472/4485/4494`), and replaced it with `checkSchemaNameRV()`, which only
+*verifies* an explicitly written name and leaves NULL as NULL. From then on the new schema
+reaches its own elements solely through a prepended `search_path` entry
+(`schemacmds.c:165-178`).
+
+### S1 — `CREATE SCHEMA "$user"` receives none of its own elements
+
+`quote_identifier("$user")` yields `"$user"`; `SplitIdentifierString` strips the quotes; and
+`preprocessNamespacePath` (`src/backend/catalog/namespace.c:4211`) compares
+`strcmp(curname, "$user") == 0` and substitutes the schema named after `GetUserId()`. The
+new schema therefore never enters the path it was supposed to head.
+
+```sql
+CREATE SCHEMA "$user" CREATE TABLE t (a int);
+SELECT relnamespace::regnamespace AS t_landed_in FROM pg_class WHERE relname = 't';
+```
+
+Observed on `new` (`/home/nm/src/pg/csxrun/evidence/PLAN-oracle-new.txt`,
+`replC08-clean-new.txt` Y1): `CREATE SCHEMA`, then `t_landed_in` = `public`, and the schema
+that was named is left empty. Variants, all verified: with a schema named after the current
+role present, the elements land *there*; with `search_path = pg_temp, public` a plain,
+non-`TEMP` `CREATE TABLE` element comes out `relpersistence = 't'` in `pg_temp_0` and vanishes
+at session end (`refC08-r7-new.txt`); with a genuinely empty path the statement dies
+`ERROR: no schema has been selected to create in` for the schema it just created itself;
+and a `CREATE OR REPLACE VIEW`/`FUNCTION` element silently overwrites the object of that name
+in `public` (`refC08-r3-new.txt` §E). Self-contradiction: the *qualified* spelling
+`CREATE SCHEMA "$user" CREATE TABLE public.tq (a int)` is rejected with
+`ERROR: CREATE specifies a schema (public) different from the one being created ($user)`
+while the unqualified spelling silently does exactly what the guard forbids
+(`replC08-x5-new.txt` U1/U2).
+
+`sql_mismatch = true`: ISO/IEC 9075-2 5.4 SR 4)b) (found.txt:8701-8702) reads verbatim
+"If the <local or schema qualified name> is contained in a <schema definition>, then the
+<schema name> that is specified or implicit in the <schema definition> is implicit." With
+`<table name> ::= <local or schema qualified name>` (found.txt:8509) and 11.1 GR 1)e)
+(found.txt:26007), `CREATE SCHEMA "$user" CREATE TABLE t (a int)` shall create `"$user".t`.
+Three scope limits: SR 4 covers only names *without* a qualifier; it reaches only object
+kinds the standard has, so it must **not** be cited for the `CREATE INDEX`, aggregate,
+operator or text-search instances; and 11.1 SR 6/SR 8 do **not** license the prepended path
+as the *placement* mechanism, because SR 8 (found.txt:25984-25986) scopes the SQL-path to
+"unqualified <routine name>s that are immediately contained in <routine invocation>s", not to
+where an element's own name is created.
+
+`is_new = false`, decisively: `old` and `new` agree on every variant expressible in
+pre-d516974 syntax, and `pre` is correct on all of them
+(`adjC08-new.txt` / `adjC08-old.txt` / `adjC08-pre.txt`; `refC08-r3-{new,old,pre}.txt`).
+`CREATE TABLE` alone reproduces the whole thing on `old`. Severity low, and the trigger is
+narrower than it first appears: if a role literally named `$user` is the effective user, the
+macro resolves to the new schema and everything is correct, so the `AUTHORIZATION`-derived
+route — the only way such a schema plausibly arises without deliberate intent — is safe
+(`refC08-r6-new.txt` §V/§V2/§V3). Exactly two schema names are affected (`$user` and
+`pg_temp`); `a,b`, `a"b`, `PUBLIC`, `select`, `$USER`, `$user2`, ` $user`, `üser`, embedded
+newlines and 63-byte names all land correctly (`refC08-r4-new.txt`).
+
+**A test for S1 is on this branch** because it is cheap and unambiguous, clearly labelled as
+a9c350d's: `src/test/regress/{sql,expected}/create_schema.{sql,out}` block at
+`sql/create_schema.sql:213-238`, hunks `@@ -376,14 +376,14 @@` and `@@ -392,13 +392,12 @@`.
+Both halves run inside `BEGIN ... ROLLBACK` so that neither the buggy nor the fixed behaviour
+leaves objects in another schema. The oracles are real: the first half's expected output is
+master's own behaviour run as a role literally named `$user`; the second half's is build
+`pre` (`/home/nm/src/pg/csxrun/evidence/PLAN-oracle3-pre.txt`).
+
+### S2 — the session temp schema outranks the new schema for unqualified names
+
+`finalNamespacePath` (`namespace.c:4291-4305`) sets `*firstNS` (the creation namespace) from
+the explicit path, then `lcons()`es `myTempNamespace` and `pg_catalog` onto the *front* of
+the lookup list. So `CREATE SCHEMA k CREATE TABLE s (y int) CREATE INDEX ON s (y)` indexes a
+pre-existing *temp* table named `s` rather than the table the first element just created.
+This is the most severe item in the audit: it reaches a genuine confused deputy, verified on
+`old` (`/home/nm/src/pg/csxrun/evidence/adjC07-G-old.txt`), where an unprivileged caller with
+a colliding temp table makes a superuser-owned SECURITY DEFINER `CREATE SCHEMA` attach a
+trigger to their own temp table. It is nevertheless **not a d516974 finding and arguably not
+a defect at all**: every symptom reproduces with `SET search_path` and no `CREATE SCHEMA`
+anywhere, byte-identically on `new`, `old` and `pre`
+(`ref-C07-ROUT-new.txt`, `ref-C07-RSD-{new,pre}.txt`), and both halves are documented.
+`doc/src/sgml/config.sgml:10349-10353`: "If it is not listed in the path then it is searched
+first (even before `pg_catalog`). However, the temporary schema is only searched for relation
+(table, view, sequence, etc.) and data type names. It is never searched for function or
+operator names." — one paragraph that predicts every symptom, including that functions and
+operators are immune. And `doc/src/sgml/ref/create_function.sgml:800` prescribes "write
+`pg_temp` ... as the last entry in `search_path`", which defeats the escalation entirely.
+No test.
+
+### S3 — an unqualified relation reference in an index or trigger element binds through `search_path`
+
+`CREATE SCHEMA s CREATE INDEX ON public_tab (a)` puts the index in `public`. Verified on
+master (`/home/nm/src/pg/csxrun/evidence/PLAN-oracle-new.txt`): `CREATE SCHEMA`, then
+
+```
+ relname | nspname
+----------------+---------
+ cs_outside_idx | public
+ cs_outside_tab | public
+```
+
+Two limits on the standard argument. The repro's object type — an index — does not exist in
+the standard at all, so 5.4 SR 4)b) cannot be cited for it. And where SR 4)b) does apply, the
+name at issue is a *reference*, so the standard's outcome would be that the reference denotes
+`<new schema>.public_tab` and the statement **errors** — not that it binds in `public`.
+`IndexStmt` (`parse_utilcmd.c:4194`) and `CreateTrigStmt` (`:4203`) are the only two element
+types whose `RangeVar` is a lookup rather than a creation name, and both are pre-d516974
+element types. a9c350d; no test (a test would have to invent a design decision).
+
+### S4 — user code run by an element can hijack the temporary `search_path`
+
+A plain (non-`LOCAL`) `SET search_path` executed by code an element invokes rewrites
+`CreateSchemaCommand`'s own GUC stack entry from `GUC_SAVE` to `GUC_SET`
+(`guc.c:2055-2065`), and `AtEOXact_GUC` restores only a `GUC_SAVE` entry (`guc.c:2212`), so `AtEOXact_GUC(true, save_nestlevel)` at `schemacmds.c:237` keeps the
+user's value: the private path leaks into the session, and every element after the hijack is
+created wherever the path now points, while the statement still reports `CREATE SCHEMA`.
+Reachable on `old` with bare built-ins and pre-d516974 element types only —
+`FOR VALUES IN (length(set_config('search_path','public',false)))`
+(`/home/nm/src/pg/csxrun/evidence/ADJ-C10-C-old.txt`). `DefineIndex` guards against exactly
+this (`indexcmds.c:1314-1319`, "Roll back any GUC changes executed by index functions, and keep subsequent changes local to this command");
+`CreateSchemaCommand` has no equivalent — but neither does `CREATE EXTENSION`, which uses the
+byte-identical idiom (`extension.c:1321/1362/1503`) and exhibits both halves inside one
+top-level statement on `pre` (`refute-C10.txt` TRANSCRIPT 5). a9c350d, at most; no test.
+
+### S5 — a forward reference inside one statement silently binds a different object
+
+Because the temporary path retains the caller's whole path, an element naming an object the
+same statement defines *later* binds a same-named object from the caller's path, while a later
+element binds the new one. Six instances measured on master (`H01-b-new.txt` G/H,
+`H01-e-new.txt` O1/O2, `H01-h-new.txt` S1b), and demonstrable on `old` with a `CREATE VIEW`
+element (`H01-c-*.txt` P5: binds `public.fwd` on new/old, `sfwd.fwd` on `pre`). a9c350d's own
+commit message says such forward references "used to work and no longer will"; the accurate
+statement is that they fail when nothing same-named is reachable and silently rebind when
+something is. What d516974 adds is blast radius: a wrong binding can now be frozen into an
+aggregate's `SFUNC`, an operator's implementation function, a TS dictionary's template or a
+`BEGIN ATOMIC` body rather than only a view's rewrite rule. No test.
+
+---
+
+## 5. Examined and found correct
+
+This is what makes the short findings list credible. Sixteen hunt lenses (H01-H16) and six
+recon dossiers ran roughly 300 probe transcripts across the three builds; the entries below
+are negative results, each backed by a cited transcript. The cassert server-log grep
+(`PANIC|FATAL|TRAP|Assert|server closed|terminating`) was empty for every run.
+
+**Grammar and reachability.** All 21 newly-reachable statement productions were executed and
+verified to reach execution (`R1-grammar-1.txt`, 26 cases d01-d18/f01-f06/n01-n02). Every command
+name in the doc's clause list is reachable from `schema_stmt` and vice versa
+(`H15-forms-new-1.txt`, 52 schemas created). `transformCreateSchemaStmtElements` handles
+exactly 12 node tags and the grammar produces exactly 12, so its `default: elog(ERROR,
+"unrecognized node type")` is unreachable — established by enumeration and by an empirical
+sweep (`H09-f` F3).
+
+**Catalog contents, dependencies, ownership.** For every `CREATE TYPE` spelling, `pg_type`
+(14 columns), `pg_range` and `pg_depend` are byte-identical inside vs outside `CREATE SCHEMA`
+(`H05-i-new.txt` S2). A complete side-by-side of all newly-allowed object types created
+inside `CREATE SCHEMA ... AUTHORIZATION regress_su` vs at top level under `SET ROLE regress_su`
+matches on all 23 catalog rows and all 13 `pg_shdepend` `deptype='o'` entries
+(`h02-h-new.txt` H3/H4). A `CREATE SCHEMA`-built domain records exactly the same dependency
+set as the fully-qualified outside control (`h06-f-new.txt` F1). Shell operators created via
+`COMMUTATOR`/`NEGATOR` land in the new schema, are owned by the `AUTHORIZATION` role, and
+round-trip through pg_dump (`H07-b-1.txt`, `H07-d-1.txt`).
+
+**pg_dump / pg_restore / pg_upgrade.** 28 elements built two ways — one `CREATE SCHEMA` and
+the same objects as separate commands under the same path — produce identical `pg_dump`
+output after schema-name normalisation, and a full dump → fresh database → re-dump is
+likewise identical, with a 92-row catalog census matching (`H13-dump-new-1.txt`). In both
+comparisons the *only* differing lines are pg_dump's random `\restrict`/`\unrestrict`
+nonce, which is regenerated per dump; the transcript labels those two diffs `DIFFERENT:` and
+`ROUND TRIP DIFFERS:` for that reason alone. A hard round trip
+with cross-referencing objects (`BEGIN ATOMIC` functions over a same-schema table and domain,
+an aggregate over an atomic function, an operator over it, a view over both, a TS
+parser+dictionary+configuration with a mapping, `COLLATION c2 FROM c1`, a range type with
+`SUBTYPE_OPCLASS`/`COLLATION`/`MULTIRANGE_TYPE_NAME`, and a shell operator left by an
+unresolvable `NEGATOR`) restores with zero errors and re-dumps identically
+(`H13-hardrt-new-2.txt`). pg_dump never emits `CREATE SCHEMA` with elements, so nothing
+round-trips through the new grammar. `pg_dump --binary-upgrade` → restore under `postgres -b`
+gives 0 restore errors, and preset binary-upgrade OIDs are consumed by the right objects
+inside `CREATE SCHEMA` exactly as outside (`H14-bu-1.txt`, `H14-bu-3.txt`).
+
+**Event triggers, deparse, hooks.** Every element type produces a
+`pg_event_trigger_ddl_commands()` row for the element's own object, with byte-identical
+`command_tag`, `object_type`,
+fully-qualified `object_identity`, correct `schema_name` (including reporting `public` for an
+element that lands in `public` via the S3 mechanism), `objsubid = 0`, and rows in source order
+(`h10-p1-new`, `h10-p8-new`, `h10-p3-new`). Side-effect objects — array types, multiranges,
+range constructors, implicit PK/unique indexes, domain constraints, the composite `pg_class`
+row, self-commutator shells — are reported identically inside and outside (`h10-p2-new`).
+`test_ddl_deparse` types and tags all 11 tested kinds correctly. `ProcessUtility_hook`,
+`OAT_POST_CREATE` and `OAT_NAMESPACE_SEARCH` see every sub-command individually, nested inside
+the `CREATE SCHEMA` (`h10-p5-new`). `in_extension` is correct for every element inside an
+extension script (`h10-p4b-new`).
+
+**Extensions.** The member set for a schema built inside `CREATE SCHEMA` in an extension
+script is byte-identical to the same objects built by top-level statements in the same script
+(`diff` empty); `DROP EXTENSION` leaves 0 orphans and 0 dangling `pg_depend` rows;
+`ALTER EXTENSION ... ADD/DROP` round-trips; `ALTER EXTENSION ... UPDATE` records 14 new
+members; the script's `search_path` is correctly restored after the element block
+(`H14-ext-1..5`).
+
+**GUC and identity handling.** `search_path` and `current_user` are restored after success,
+after failure, after `ROLLBACK TO SAVEPOINT`, after a caught plpgsql `EXCEPTION`, and after
+2000 caught failures in one `DO`; no "GUC nest level" warning was ever emitted
+(`h03-a-new.txt`, `H16-j-new.txt`, `H16-r-new.txt`). Odd `search_path` values (`''`, a
+nonexistent schema, embedded whitespace, quoting) behave. `SET LOCAL` inside an element is
+correctly discarded. `681d9e4`'s `NewGUCNestLevel`/`GUC_ACTION_SAVE` mechanism nests
+correctly for the new element types even under `SECURITY_RESTRICTED_OPERATION` +
+`RestrictSearchPath()` — the CVE-2023-2454 shape was re-run for this commit's object types
+inside `REFRESH MATERIALIZED VIEW` and is clean, with the restricted path intact immediately
+after the `CREATE SCHEMA` returns and every object in its own new schema
+(`h08-secrest-new.txt`).
+
+**Unusual outer contexts (E7).** 30 theories, all negative: plpgsql `EXECUTE` and static
+statements, SQL-language function bodies, `SECURITY DEFINER`, procedure transaction control,
+read-only SPI, savepoints, `PREPARE TRANSACTION` (an 11-element body prepares, commits and is
+fully usable), concurrent duplicate schema creation, lock timeouts mid-list, binary-upgrade
+mode, `debug_discard_caches = 1`, row triggers, event-trigger functions, extended query
+protocol, `BEGIN READ ONLY`, `SERIALIZABLE`, and single-user mode (`H14-ctx-*`, `H14-2pc-1`,
+`H14-bu-*`).
+
+**Diagnostics.** 73 adversarial statements covering every newly-allowed element type, each
+run inside `CREATE SCHEMA` and again at top level under an equivalent `search_path`, produced
+**0 differing diagnostics** (`H16-sweep-new.txt`). Error positions that are emitted are
+correct, including at byte offset ~215 000 for element #8001 (`H16-s-new.txt` S3) and inside
+an extension script, plpgsql static/`EXECUTE`, SQL functions and mid-way through a
+multi-statement simple query (`H14-pos-1.txt`).
+
+**Robustness.** 4000 `CREATE DOMAIN` elements take 0.13 s with `MessageContext` flat at
+64 KB; 100 000 elements ends with a clean "out of shared memory" hint; a 4000-deep domain
+chain works; 100-level recursive `CREATE SCHEMA` restores outer state; self-referential
+definitions all give ordinary errors; 1700-column composites and 101-argument functions give
+the documented limits identically inside and outside; a 10 000-label enum and a 1 MB function
+body are fine (`H16-d2/k/a-1/r-new.txt`).
+
+**Client tools.** ecpg preprocesses, compiles and runs a 28-object `CREATE SCHEMA` correctly;
+its two limitations (`$n` inside a dollar-quoted body; a host variable in a DDL element)
+reproduce identically outside `CREATE SCHEMA`. Independently re-verified this pass: 14
+element shapes preprocessed twice each (as a clause and standalone) all have `in == out`; the
+two failures (`BEGIN ATOMIC` bodies; a `###` operator name taken as a C preprocessor
+directive) fire identically outside (`CLIENT-ecpg-verify-new.txt`). psql's `\d`-family
+describe commands are byte-identical for an object made by an element vs by a standalone
+statement — `\df \da \do \dT \dD \dO \dF \dFd \dFp \dFt \d` and their `+` forms
+(`H12-desc-1-new.txt`). psql `--single-transaction`, `-c`, `\i`, `\ir`, `:var`
+interpolation, `\gexec`, `\if/\else/\endif`, `\g`, comments, dollar quotes and `COPY FROM
+STDIN` after a correctly-split `CREATE SCHEMA` all behave (`H11-*`, `H13-psqlmodes-new-1.txt`).
+
+**Composition (checked by the completeness critic, since no lens owned the seam).** A trigger
+function and its trigger in the same statement bind correctly; views, column `DEFAULT`s and
+`CHECK`s over sibling functions all deparse qualified; a forward reference from a trigger
+element to a later function element binds `public` — but so does the documented
+separate-commands equivalent, so it is symmetric. A sibling plpgsql trigger function whose
+unqualified `INSERT` writes to `public.audit_log` is likewise symmetric with the documented
+equivalent (`CRIT-2-compose-new.txt`, `CRIT-2b-trigger-new.txt`).
+
+**Bounded negatives worth recording.** Non-core PL validators run arbitrary code at DDL time
+inside the `CREATE SCHEMA` environment (a Perl `BEGIN` block fires during
+`plperl_validator`), but the channel is closed one layer down: PL/Perl refuses SPI during
+compilation, so that code cannot read `search_path`/`current_user`, cannot `SET search_path`
+to redirect later elements, and cannot `DROP SCHEMA` the schema being created — all three
+die with `ERROR: SPI functions can not be used during function compilation` and the whole
+statement rolls back (`CRIT-1-plperl-new.txt`). And
+`information_schema.domains.domain_default`'s frozen deparse (see §6, C09) is the *only*
+instance of that class in `information_schema`: `routines.routine_definition` is raw `prosrc`
+(`information_schema.sql:1589`), not a deparse at all, while
+`parameters.parameter_default` uses `pg_get_function_arg_default` and
+`attributes.attribute_default` uses `pg_get_expr`, both of which deparse at read time
+(`CRIT-4b-infoschema-new.txt` plus `information_schema.sql:294/1022/1188`).
+
+---
+
+## 6. Candidates investigated and rejected
+
+Nineteen candidates went through hunt → replicate → adjudicate → refute. Sixteen were
+rejected. This is the most useful section for judging how much to trust the four that
+survived: in almost every case the killer was the same control — *run the same thing outside
+`CREATE SCHEMA`, or on `pre`, and watch it fail identically*.
+
+| id | candidate | why rejected |
+|---|---|---|
+| C01 | Text-bodied routine element validated against the temporary path: accepted where the documented equivalent is rejected, then dead or silently wrong | Every symptom reproduces with a plain `SET search_path` and no `CREATE SCHEMA` (`refute-C01-p1-new.txt` T1-T6, T9), including the "strongest exhibit" — identical bodies binding differently for `BEGIN ATOMIC` vs quoted (T5). It is documented, intended behaviour of `AS '<text>'` bodies (`create_function.sgml:614-627`). The same server-manufactured invisible path has shipped in `CREATE EXTENSION` since 2011 (`extension.c:1362`) and produces all three symptoms on `pre` (`refute-C01-ext-pre.txt`). Two premises were false: the "accepted inside, rejected outside" asymmetry already holds on `old`/`pre` for `CREATE VIEW`, and the pre-d516974 element set is *not* entirely early-binding (a `nextval('sq'::text::regclass)` default is stored as a name and dies in a fresh session on `old` and `pre`). |
+| C02 | Aggregate `INITCOND`/`MINITCOND` validated under the temporary path and stored as raw text: unrestorable dump, pg_upgrade failure after `--check` said compatible | Reproduced with **no `CREATE SCHEMA`, the default `search_path`, and a fully qualified `INITCOND`** on `old`: `pg_dump` emits the aggregate before the table it names, restore loses it silently (`surviving_user_aggregates = 0`, psql exit 0), `pg_upgrade --check` says "Clusters are compatible" and the real run fails (`refuteC02-C-old.txt`). Inside and outside are indistinguishable down to `pg_depend` and pg_dump bytes (`refuteC02-D-new.txt` N1). The doc's own "equivalent way" qualifies internal cross-references; under the doc's actual recipe inside and outside agree exactly (`refuteC02-E-doc.txt`). |
+| C04 | Thesaurus sub-dictionary named in an element stored as unqualified raw text: dead outside the statement, retargetable, lost by pg_dump | The raw-text storage, the missing dependency, the retargetability and the pg_dump gap are all properties of `CREATE TEXT SEARCH DICTIONARY` itself: the whole chain reproduces on `pre` with nothing but `SET search_path = z, public` (`repl-C04-attr-pre.txt`). What is left attributable to d516974 is the loss of one create-time error message in one narrow shape, and the first symptom a user sees is a loud, immediate `text search dictionary "..." does not exist` on the very next statement. |
+| C05 | `checkSchemaNameList` guards only the element's own name, so elements create objects in other schemas including `pg_catalog` and `pg_temp` | The same statements standalone on master give byte-identical placement; the unqualified case is actually *better* inside. On `old`, pre-existing element types do everything the candidate calls new, and worse: `CREATE VIEW` over a temp table silently relocates the element's own object into `pg_temp`; a `CREATE TABLE` with a `pg_temp`-schema column type leaves a permanent table that loses its only column at session end. The headline damage (a permanent range type destroyed at session end) reproduces on `pre` with a standalone `CREATE TYPE`. |
+| C06 | An element can drop the schema being created; `CREATE SCHEMA` still reports success and `pg_dump` then fails for the whole database | The documented separate-commands form gives a byte-identical outcome in the same session, and reproduces verbatim on `old` and `pre`. It needs no aggregate, no `INITCOND`, no domain and no user code: two ordinary concurrent sessions (`BEGIN; CREATE FUNCTION s.f() ...` / `DROP SCHEMA s;` / `COMMIT;`) orphan a `pg_proc` row on `pre` and break `pg_dump` for the database (`refC06-B-pre.txt`). It is a 14-year-old explicitly-deferred gap: `1575fbc` (2012) fixed relations only and says "We need similar protection for all other object types ... I'm leaving that for a separate commit." |
+| C07 | Session temp namespace outranks the new schema (see §4 S2) | Identical outside `CREATE SCHEMA`, identical on `pre`, and documented twice (`config.sgml`, `create_function.sgml`). a9c350d at most; kept in §4 as context. |
+| C09 | Domain `DEFAULT` deparsed under the temporary path and stored unqualified, so `information_schema.domains` and `\dD` report a default meaning a different function | Reproduces with the plainest top-level `CREATE DOMAIN public.dd AS int DEFAULT f();` under the default path, byte-identically on `new` and `pre`, and every ordinary domain's `domain_default` is likewise not dump/restore-stable. Documented as known since `3666260` (2006) — "default values for domains really need to be dumped by decompiling the typdefaultbin expression, not just printing the typdefault text which may be out-of-date or assume the wrong schema search path" — and the sibling `adsrc`/`consrc` columns were deleted for the same reason in `fe50382`. The rule has exactly one input (is the referenced object visible in the live path), so "inside stores the wrong text, outside the right one" is false in both directions. |
+| C10 | `CreateSchemaCommand` does not defend its temporary path against user code (see §4 S4) | Full misplacement plus full leak on `old` with only built-ins and pre-d516974 element types; `CREATE EXTENSION` exhibits both halves on `pre` with a bare `SET` and no function at all. a9c350d at most. |
+| C11 | Unqualified names resolve differently from the documented equivalent in both directions | Part (a) is a9c350d (a VIEW/TABLE-only script gives the same wrong binding on `old`, the right one on `pre`); parts (b) and (c) are identical on all three builds. The doc's own "equivalent way" form and the prepended path outside `CREATE SCHEMA` produce the *same* binding as inside. |
+| C12 | Every `role`/`session_authorization` `SET`/`RESET` clause rejected under `AUTHORIZATION` (overlaps D2) | Not rejected as a phenomenon — it is D2 — but rejected as an *independent* candidate, because its own probes established that the rule violation predates the series: `adjC12-a3-pre.txt` reaches the byte-identical false message on `pre` using only pre-d516974-legal elements, and `adjC12-a4-pre.txt` reaches it with no `CREATE SCHEMA` at all inside a genuine SECURITY DEFINER function. §D2 carries the finding with that caveat stated. |
+| C13 | Shell/base types and TS parser/template cannot be created under `AUTHORIZATION <non-superuser>` even when the invoker is a superuser | Holding the effective user fixed — the one thing `AUTHORIZATION` is defined to change — the behaviour is byte-identical to top level under `SET ROLE`. 11 of 16 probed element forms succeed under `AUTHORIZATION regress_bob`; the 5 that fail do so through each command's own long-standing superuser gate (`typecmds.c:221`, `tsearchcmds.c:197/704`, `functioncmds.c:1162`), none of which d516974 touches. On `pre` a superuser was already blocked from two of the six then-allowed element types by naming a non-superuser in `AUTHORIZATION`. What survives is a documentation nit only. |
+| C15 | `DefineRange` multirange naming: an internal `elog` (XX000) and a raw unique-index violation reach the user; constructors land in the wrong schema | Real, but pre-existing (`6df7a96`, v14) and not about `CREATE SCHEMA`: a four-case probe that never uses `CREATE SCHEMA` is byte-identical on `new`, `old` and `pre`, and one statement with zero setup on `pre` produces `ERROR: 23505: duplicate key value ... (_r, 2200)`. Decisively, three of the four sub-observations require the two namespaces to differ, which *cannot* happen through `CREATE SCHEMA`'s own environment — an unqualified `MULTIRANGE_TYPE_NAME` resolves through the prepended path into the new schema. The cross-schema case is exercised by a shipped, passing in-tree test added five weeks *after* d516974 by the CVE-2026-6472 fix `4793fc4`. |
+| C16 | Tag-filtered event triggers are silently bypassed by wrapping a command in `CREATE SCHEMA` | The same bypass occurs with no `CREATE SCHEMA` anywhere, identically on all three builds: a `WHEN TAG IN ('CREATE SEQUENCE')` deny trigger does not fire for `CREATE TABLE t (a serial)`, and a `CREATE INDEX` deny trigger does not fire for `CREATE TABLE ... PRIMARY KEY`. The invariant is "tag-filtered `ddl_command_start` has never fired for nested DDL", from `5525e6c` (2013). PostgreSQL's own documented DDL-gating recipe (an *unfiltered* start trigger) still works, and at `ddl_command_end` nothing is hidden. Residue: a documentation patch to `doc/src/sgml/event-trigger.sgml:89-101`, whose exception list for `ddl_command_start` names only shared objects and event-trigger commands. |
+| C17 | The wrong-schema error for 20 of the 21 added forms has no `LINE`/caret | Outside `CREATE SCHEMA` these same commands are equally caret-less — only `CREATE TABLE` gets one at top level — so the documented equivalence is satisfied, and for three forms the inside diagnostic is strictly *better*. The commit states the limitation in its own comment (`parse_utilcmd.c:4323`, "Sadly, this also means we don't have a parse location to report") and its own regression test records the caret-less output as expected. Diagnostic-quality limitation, self-acknowledged; cosmetic. |
+| C18 | Tab completion, superset of D4 | The parts that survived are §D4; the rest was refuted (see D4's counterarguments): the invariant was never universal, un-anchoring is a documented backlog, and the "misleading table list" is generic pre-existing psql behaviour. |
+| C19 | `create_schema.sgml`'s "all the created objects will be owned by that user" is false for two newly added element types | The premise "before d516974 the claim held for every legal element type" is false: on `old`, `CREATE SCHEMA idxs1 AUTHORIZATION aj_bob CREATE INDEX carol_idx1 ON carol_tab (a)` produces an index owned by `aj_carol`, and on `pre` the sentence is already false via `CHECK` constraints and triggers. `CREATE TYPE ... AS RANGE` produces byte-identical catalogs inside and outside. Longstanding doc imprecision (the sentence is 3450fd08/2003), not this commit's. |
+| O2 | A `PGC_SUSET` GUC in a routine element's `SET` clause is checked against the `AUTHORIZATION` role, not the invoking superuser | **Adjudicated intended.** `CREATE FUNCTION topg1() ... SET log_min_duration_statement = 1` issued at top level *by that same non-superuser role* fails with the identical `ERROR: permission denied to set parameter "log_min_duration_statement"` (`PLAN-o2ctl-new.txt`), and matches `SET ROLE` in all four sub-cases including after `GRANT SET ON PARAMETER` (`h02-d-new.txt` D2). Since the documented contract is "objects will be owned by that user", checking against that user is coherent; there is no output a correct implementation demonstrably ought to produce, so no test was written. Contrast D2, where the top-level control *succeeds* for the same role. |
+| O4 | Unqualified `CREATE INDEX`/`CREATE TRIGGER` element binds outside the new schema | Behaviour confirmed, but "correct" is undecidable without inventing a design (error? bind in the new schema? create the index in the new schema over a `public` table?), and it is a9c350d's, sharing S1's mechanism. Reported as §4 S3; no test. |
+
+---
+
+## 7. Limitations — what was not examined
+
+Taken from the completeness critic's pass; each item is a real gap in this audit.
+
+**Newly-allowed command forms.** At the grammar-production level, none was skipped: all 21
+were executed, and every variant the coverage dossier listed as untested was later run by some
+lens (prefix/unary operators, pre-8.2 aggregate syntax, ordered-set / hypothetical-set /
+moving aggregates, `RETURNS TABLE`, the no-`RETURNS` OUT-parameter form, `WINDOW`,
+`TRANSFORM FOR TYPE`, `SUPPORT`, `LANGUAGE c` against a real shared library, all four
+`CREATE COLLATION` forms, `CREATE TYPE ... (LIKE=...)`, `CANONICAL`, the full domain
+`ColQualList`). Below production level, one gap survived to the critic and was then closed:
+no lens had used a non-core procedural language as an element `LANGUAGE` (plperl/plpython/pltcl
+*are* installed, contrary to one lens's stated gap) — closed, negative (§5).
+
+**Environment properties E1-E7.** E1, E2 and E7 are saturated. E3's second consequence
+(`isTopLevel` gating `PreventInTransactionBlock`) was closed by grep, not by a probe: no
+newly-allowed command calls it, so the only `isTopLevel`-sensitive element is the pre-existing
+`CREATE INDEX CONCURRENTLY`. E4's `canSetTag = false` and `planOrigin = PLAN_STMT_INTERNAL`
+were probed by nobody and closed by source reading: `planOrigin` has no in-core consumer, and
+the only out-of-core one (`pg_stat_statements.c:1487-1489`) tests only for
+`PLAN_STMT_CACHE_GENERIC`/`CUSTOM`. Four further E2/E7 sub-paths were answered only by
+reading source: `GucStack.srole` (not exposed to SQL); `push_old_value`'s
+`Assert(stack->state == GUC_SAVE)` for `GUC_ACTION_SAVE` (no input reaches it); whether any
+user-sensitive GUC *assign* hook can hold a stack entry at `CreateSchemaCommand`'s nest level;
+and whether a subtransaction opened inside an element leaves the GUC nest level inconsistent.
+
+**Not exercised at all.**
+* `sepgsql` — not built (no `--with-selinux`); `test_oat_hooks` was substituted, so
+ `OAT_POST_CREATE` for the ~15 new object types under E1's identity switch is unexercised by
+ a real MAC module.
+* An `InvokeNamespaceSearchHook` returning false for the new schema (`namespace.c:4281`). This
+ is the one code path that would silently relocate the creation namespace — S1's failure mode
+ by a second route, with no `$user` name required. Nobody wrote the C hook.
+* Any C-level SPI caller (`worker_spi.c:107` is the only in-tree SPI `CREATE SCHEMA` with
+ elements, and it was never run on any build).
+* `table_rewrite` event triggers (no allowed element can rewrite a table) and
+ `session_replication_role = replica` with `ENABLE REPLICA` triggers.
+* psql `\copy`, `\e`, `\ef`, `\ev`. `\e` re-feeds the edited buffer through `psql_scan`, which
+ is D1's wedge with an editor in the loop.
+* DDL-deparse consumers: no in-core deparser exists, so nobody could observe what an extension
+ deparsing a stashed element parse tree would emit. `checkSchemaNameRV`/`checkSchemaNameList`
+ only *validate*, so the stashed tree carries an unqualified name whose meaning depends on the
+ temporary path.
+* The interactive-terminal consequence of D1 was found only in the completeness pass; it did
+ **not** go through the refute stage, and it was not run on `pre`.
+
+**Structural weaknesses of the audit itself, stated so they can be discounted.** Ten of the
+sixteen lenses (H01, H02, H05, H07, H08, H09, H11, H12, H13, H15) wrote no coverage-gaps
+section at all — including the three that produced the most surviving material. Three of the
+six that did wrote gaps that were factually wrong about the environment (claiming
+`pg_stat_statements` unavailable when another lens had used it; claiming plperl/plpython not
+built when they are installed; claiming no way to run arbitrary user code at DDL time using
+only the new element types, when a plperl validator does exactly that). Lens boundaries were
+drawn per object type, so "new element × old element" compositions were owned by nobody until
+the completeness pass ran them (negative). Finally, the `new` install tree is contaminated:
+`/home/nm/src/pg/csx-inst/share/postgresql/extension/` still holds four lenses' leftover
+extensions (`csxfail`, `csxg`, `csxh05`, `csxok`), so any future probe enumerating
+`pg_available_extensions` on `new` will see them.
+
+---
+
+## 8. How to reproduce everything
+
+### Builds
+
+```
+/home/nm/src/pg/csx-audit 6885b845b4ba master, this audit branch (create-schema-defect-tests)
+/home/nm/src/pg/csx-old 404db8f9edbb d516974~1
+/home/nm/src/pg/csx-pre 1ff3180ca016 a9c350d~1
+```
+
+Installed trees: `/home/nm/src/pg/csx-inst`, `csx-old-inst`, `csx-pre-inst`.
+Build dirs: `/home/nm/src/pg/csx-build`, `csx-old-build`, `csx-pre-build`.
+
+### Running a probe on any build
+
+```
+/home/nm/src/pg/csxrun/pgrun.sh <new|old|pre> file.sql [more.sql ...] # SQL
+/home/nm/src/pg/csxrun/pgsh.sh <new|old|pre> script.sh # shell
+```
+
+Each spins up a throwaway cluster (`initdb -A trust`, `fsync=off`), runs the input with
+`psql -X -e -v ON_ERROR_STOP=0`, prints the output, destroys the cluster, and greps the server
+log for `PANIC|FATAL|TRAP|Assert`. Extra server options via `PGRUN_OPTS` (e.g.
+`PGRUN_OPTS="-c log_statement=all"`), extra initdb options via `PGRUN_INITDB_OPTS`.
+`pgsh.sh` exports `BIN`, `PGHOST`, `PGUSER`, `PGDATABASE` and puts `BIN` on `PATH`.
+**Always use `psql -X`**: the repository owner's `~/.psqlrc` enables per-statement rollback,
+which masks transaction-abort behaviour and would mislead on D1 in particular.
+
+### The four findings, one command each
+
+```
+# D1 (psql lexer) -- expect: VACUUM error, exit 3, cs_gb absent; oracle and control clean
+/home/nm/src/pg/csxrun/pgsh.sh new /home/nm/src/pg/csxrun/probe/client-c03/oracle.sh
+# D1 discriminator vs the case 049b742 deferred
+/home/nm/src/pg/csxrun/pgsh.sh new /home/nm/src/pg/csxrun/probe/xprem/p6.sh
+# D2 (SET role under AUTHORIZATION)
+/home/nm/src/pg/csxrun/pgrun.sh new /home/nm/src/pg/csxrun/probe/plan/o1o2.sql
+/home/nm/src/pg/csxrun/pgrun.sh pre /home/nm/src/pg/csxrun/probe/adjC12/a3.sql # attribution
+# D3 (PL/pgSQL) -- run on all three builds
+/home/nm/src/pg/csxrun/pgrun.sh new /home/nm/src/pg/csxrun/probe/adjC14/adj.sql
+/home/nm/src/pg/csxrun/pgrun.sh old /home/nm/src/pg/csxrun/probe/adjC14/adj.sql
+# D4 (tab completion) -- needs a pty; see the shipped TAP test below
+# S1 (a9c350d, "$user")
+/home/nm/src/pg/csxrun/pgrun.sh new /home/nm/src/pg/csxrun/probe/plan/oracle.sql
+/home/nm/src/pg/csxrun/pgrun.sh pre /home/nm/src/pg/csxrun/probe/plan/oracle3.sql
+```
+
+### The shipped tests
+
+Six files changed on branch `create-schema-defect-tests`, +339/-0, all test inputs or expected
+files; no compiled source is touched, so **no rebuild is needed**
+(`ninja -C /home/nm/src/pg/csx-build -n` reports "no work to do"):
+
+```
+src/test/regress/sql/create_schema.sql src/test/regress/expected/create_schema.out
+src/pl/plpgsql/src/sql/plpgsql_misc.sql src/pl/plpgsql/src/expected/plpgsql_misc.out
+src/bin/psql/t/001_basic.pl src/bin/psql/t/010_tab_completion.pl
+```
+
+```
+meson test -C /home/nm/src/pg/csx-build --suite setup
+meson test -C /home/nm/src/pg/csx-build regress/regress plpgsql/regress \
+ psql/001_basic psql/010_tab_completion psql/020_cancel psql/030_pager \
+ pg_upgrade/002_pg_upgrade recovery/027_stream_regress \
+ test_plan_advice/001_replan_regress --print-errorlogs
+```
+
+Expected: exactly the seven failures tabulated in §3 and nothing else. Diffs land in
+`/home/nm/src/pg/csx-build/testrun/<group>/<name>/regression.diffs`; TAP output in the
+corresponding `log/regress_log_*`. Every added expected-output block records what a
+**correct** implementation would print, so the failure diff *is* the demonstration.
+
+To drive one regress test standalone (avoiding the schedule):
+
+```
+B=/home/nm/src/pg/csx-build; S=/home/nm/src/pg/csx-audit/src/test/regress
+export PATH="$B/tmp_install/home/nm/src/pg/csx-inst/bin:$B/src/test/regress:$PATH"
+export LD_LIBRARY_PATH="$B/tmp_install/home/nm/src/pg/csx-inst/lib/x86_64-linux-gnu"
+export INITDB_TEMPLATE="$B/tmp_install/initdb-template"
+"$B/src/test/regress/pg_regress" --inputdir="$S" --expecteddir="$S" --bindir= \
+ --dlpath="$B/src/test/regress" --dbname=regression --outputdir=/tmp/csxrun \
+ --temp-instance=/tmp/csxrun/tmp_check --port=48460 create_schema
+```
+
+(for plpgsql: `--inputdir`/`--expecteddir` `/home/nm/src/pg/csx-audit/src/pl/plpgsql/src`,
+`--dlpath "$B/src/pl/plpgsql/src"`, `--dbname regression_plpgsql`, test `plpgsql_misc`).
+
+### Evidence
+
+Every transcript this report quotes is under `/home/nm/src/pg/csxrun/evidence/`; the probe
+sources that produced them are under `/home/nm/src/pg/csxrun/probe/`. The per-lens working
+notes, including the full theory lists and negative results summarised in §5, are under
+`/home/nm/src/pg/csxrun/notes/` (`H01`-`H16`, plus recon dossiers `R1`-`R6`). Key files:
+
+| finding | evidence |
+|---|---|
+| D1 | `CLIENT-C03-oracle-new.txt`, `CRIT-3-interactive-{new,old}.txt`, `XPREM-p6-new.txt`, `ADJ-C03-A-{new,old}.txt`, `refute-C03-run{1,2,3,4}*.txt`, `FINAL-tap.txt`, `FACTCHECK-D1-{oracle,xprem6,pty,var}-new.txt` |
+| D2 | `PLAN-o1o2-new.txt`, `PLAN-o2ctl-new.txt`, `PLAN-oracle-new.txt`, `VERIFY-o1-mechanism-new.txt`, `h02-a-{new,old,pre}.txt`, `adjC12-a3-pre.txt`, `FINAL-regressdiffs.txt`, `FACTCHECK-D2-{spellings,o1o2}-new.txt`, `FACTCHECK-D2-a3-pre.txt` |
+| D3 | `adjC14-{new,old,pre}-1.txt`, `PLAN-oracle4-new.txt`, `refC14-r{1,2,3,4}*.txt`, `XPREM-p{2,4,5}*.txt`, `FINAL-regressdiffs.txt`, `FACTCHECK-D3-{new,old}.txt` |
+| D4 | `CLIENT-O3-tap-new.txt`, `PLAN-o3-die-new.txt`, `refute-C18-{new,old,pre}-*.txt`, `FINAL-tap.txt` |
+| S1 | `PLAN-oracle-new.txt`, `PLAN-oracle3-pre.txt`, `adjC08-{new,old,pre}.txt`, `refC08-r{3..8}*.txt`, `replC08-*.txt` |
+| test run | `FINAL-suiterun.txt`, `FINAL-regressdiffs.txt`, `FINAL-tap.txt`, `FINAL-gitdiff.txt`, `VERIFY-*`, `FACTCHECK-suiterun.txt` |
diff --git a/PROVENANCE.md b/PROVENANCE.md
new file mode 100644
index 0000000..19b314d
--- /dev/null
+++ b/PROVENANCE.md
@@ -0,0 +1,305 @@
+# PROVENANCE
+
+Branch `create-schema-defect-tests` is the product of an automated, model-driven audit of
+PostgreSQL commit `d516974` ("Support more object types within CREATE SCHEMA."). Everything
+on it -- the report and the regression tests -- was written by a language model. **No human
+wrote any of it, and no human has reviewed it.** This file records how it was produced so
+that a reviewer can judge the work and reproduce the verification.
+
+---
+
+## Tooling
+
+* **Tool:** Claude Code (Anthropic's agentic CLI), using its Workflow orchestration feature
+ (one deterministic script driving many subagents).
+* **Model:** Opus 5 (`claude-opus-5`), for the orchestrator and for every subagent.
+* **Run by:** the repository owner (Noah Misch), interactively, from
+ `/home/nm/src/pg/postgresql`.
+* **Workflow run ID:** `wf_784c5f4f-e9b`. Started 2026-09-02 23:19 UTC; relaunched
+ 23:33 UTC after the orchestrator rewrote the scheduler (see [Scheduling](#scheduling)).
+* **Human contribution:** the two prompts quoted below, and nothing else. The owner supplied
+ no finding, no verdict, and no line of any test.
+
+## The prompt
+
+Verbatim, as typed by the repository owner:
+
+```
+Make a large workflow, with at most 90 agents, to write test cases covering
+user-visible defects in commit d516974 that are still present in master. Use
+your own worktree; disregard the present dir except as repository to which to
+attach your worktree. The workflow should first look for extant user-visible
+defects. If it finds any, write a test case covering some of those defects.
+If any defects found weren't suitable to test, describe them in a report.
+
+The workflow should manage budget so, when it reaches a budget stop, it loses
+1-2 agents instead of 16 concurrent agents.
+
+Commit the following on a fresh branch:
+- A report describing any defects found, testable or not. Prefix the report
+ with [no defects] if that's so.
+- Any tests written
+- A PROVENANCE.md file containing model, prompt, etc.
+
+For this one, I'm wary that CREATE SCHEMA is a weird execution environment.
+I'm reminded of CVE-2023-2454 resulting from the weird environment. Commit
+d516974 means a bunch more old code needs to be ready for that weird
+environment. (Granted, the CVE-2023-2454 fix made the environment less weird.
+If we're lucky, all is now well.)
+```
+
+A second message corrected the first implementation of the budget requirement:
+
+```
+That's a terrible implementation of the budget goal. Now I won't even use my
+budget before it expires. If you can't do better than that, just run full 16
+concurrency and accept the loss.
+
+I wanted something like: start at 16 concurrent, but when one exits, estimate
+the budget burndown curve. If there's a decent chance one started now would
+fail due to budget exhaustion, don't replace the one that just exited.
+```
+
+## Environment
+
+* **Base commit:** `6885b845b4ba0b7aee09daa9817703477faa3704` -- "doc: Fix link on
+ pg_dsm_registry_allocations page." This branch is based on it; every `file:line`
+ citation in the report is against it.
+* **Audited commit:** `d516974840f4059d331ae6057ede3e4edd3c6747` -- "Support more object
+ types within CREATE SCHEMA." (Kirill Reshke, Jian He, Tom Lane; 2026-04-06).
+* **Worktree:** `/home/nm/src/pg/csx-audit`, a `git worktree` attached to the owner's clone
+ at `/home/nm/src/pg/postgresql`.
+* **Builds**, all meson, all `-Dcassert=true -Ddebug=true -Doptimization=1
+ -Dtap_tests=enabled`:
+
+ | name | source | purpose |
+ |---|---|---|
+ | `new` | `6885b84` (this worktree) | every "master" observation in the report |
+ | `old` | `404db8f` = `d516974~1` | the `is_new` dimension for d516974 itself |
+ | `pre` | `1ff3180` = `a9c350d~1` | before the whole CREATE SCHEMA series, so findings can be attributed to d516974 rather than to its two same-day siblings a9c350d and 404db8f |
+
+* **Harness:** `/home/nm/src/pg/csxrun/pgrun.sh <new|old|pre> file.sql ...` initdb's a
+ throwaway cluster under `/tmp`, starts it with `fsync=off` and a unix socket only, runs each
+ file with `psql -X -e -v ON_ERROR_STOP=0`, prints the transcript plus any PANIC/FATAL/TRAP
+ lines from the server log, then destroys the cluster. `pgsh.sh <new|old|pre> script.sh`
+ does the same but runs an arbitrary shell script with `BIN`, `PGHOST`, `PGUSER`,
+ `PGDATABASE` exported, for pg_dump round-trips, ecpg, and psql-lexer tests.
+* **SQL standard text:** `/home/nm/src/pg/gbyrun/std/found.txt`, pdftotext output of
+ `5CD2-02-Foundation-2006-01.pdf` (the 2006-01 committee draft of ISO/IEC 9075-2, which has
+ the General Rules). 11.1 `<schema definition>` begins near line 25895.
+* **Host:** Debian 13 (trixie), Linux 6.12.94 x86_64, gcc (Debian 14.2.0-19) 14.2.0,
+ meson 1.7.0, ninja 1.12.1, Python 3.13.5. Two effective CPUs (cpuset cgroup).
+* **Not part of the deliverable:** `/home/nm/src/pg/csxrun/` (recon dossiers, per-lens notes,
+ every raw transcript) and the build and install trees. Nothing on this branch depends on
+ them except the reproduction commands in the report, which a reviewer would re-create.
+
+---
+
+## The rubric
+
+The owner had corrected an earlier audit for reporting *changes* rather than *defects*
+("You're not here to be a change detector, you're here to find defects"), and gave this
+rule, which was written into the prompt of every agent in this run:
+
+Each candidate is scored on three independent dimensions:
+
+* `sql_mismatch` -- the SQL standard requires behavior master does not exhibit;
+* `per_principles` -- the commit's own principles (its commit message, the code comments it
+ wrote, the documentation it wrote, and the proposition that a command documented as
+ accepted inside CREATE SCHEMA should behave there as it does outside, apart from landing in
+ the new schema) require behavior master does not exhibit;
+* `is_new` -- the RULE VIOLATION, not the syntax, is new relative to the state before
+ d516974.
+
+**Defect iff `per_principles OR (sql_mismatch AND is_new)`.** `is_new` alone is not a
+defect. `sql_mismatch AND NOT is_new` is a pre-existing divergence that d516974 did not
+cause. The script recomputes `is_defect` from each adjudicator's own returned booleans and
+logs a warning when the adjudicator's prose disagrees with its own dimensions.
+
+Agents were also warned, in the shared prompt, against the specific failure that wrecked the
+earlier audit: reaching for a standard citation to dignify a finding that is really about
+internal coherence. Aggregates, operators, text search objects and indexes are outside the
+standard's ken, so `sql_mismatch` is false for them by construction.
+
+## Attribution
+
+d516974 landed the same day as two siblings, `a9c350d` ("Don't try to re-order the
+subcommands of CREATE SCHEMA") and `404db8f` ("Execute foreign key constraints in CREATE
+SCHEMA at the end"), and was followed by `049b742` ("psql: Tighten heuristics for BEGIN/END
+within CREATE SCHEMA"). a9c350d is the commit that made CREATE SCHEMA rely on a temporarily
+prepended `search_path` instead of rewriting each element's schema name, so several
+observations in this area belong to it, not to d516974. The `pre` build exists to settle
+that question empirically, and the report keeps sibling-commit findings in a separate
+section.
+
+## Method
+
+Nine phases, driven by one deterministic script (preserved at
+`~/.claude/projects/.../workflows/scripts/create-schema-d516974-audit-wf_784c5f4f-e9b.js`):
+
+1. **Recon** (6 agents) -- commit forensics and the commit's own principles; the execution
+ environment at code level; the backend code newly reachable from inside CREATE SCHEMA;
+ the client-side code (psqlscan.l, tab completion, ecpg); the SQL standard's
+ `<schema definition>` rules; and existing test coverage. Each wrote a dossier that later
+ phases read instead of redoing the work.
+2. **Hunt** (16 agents, one per lens) -- each lens generated at least twelve falsifiable
+ theories and then *ran* them on all three builds, reporting only observations it could
+ quote from a real transcript, plus a list of what it checked and found correct.
+3. **Triage** (1 agent) -- dedupe into a numbered candidate list, spot-checking claims
+ against their evidence files.
+4. **Replicate** -- an independent agent per candidate re-derived the observation from
+ scratch, ran it on `new`, `old` and `pre`, ran the equivalent command *outside* CREATE
+ SCHEMA as a control, minimized the reproduction, and named the mechanism with file:line.
+5. **Adjudicate** -- one agent per replicated candidate applied the rubric.
+6. **Refute** -- one adversarial agent per surviving defect, instructed to kill it and to
+ default to refuted when uncertain.
+7. **Coherence** (2 agents) -- a shared-premise pass (when several findings rest on one
+ claim, adjudicate the claim once, adversarially) and a completeness critic whose answer
+ becomes the report's limitations section.
+8. **Tests** (5 agents, serial) -- plan, write core regression tests, write client-side
+ tests, adversarially verify, polish.
+9. **Report** (2 agents) -- write, then fact-check every quoted transcript, file:line
+ citation and standard citation by re-running and re-reading.
+
+Phases 4 to 6 run as one pipeline per candidate, so a candidate can be under refutation
+while another is still being replicated.
+
+## Scheduling
+
+The owner asked that a budget stop cost 1-2 in-flight agents rather than 16. The first
+implementation ran everything in fixed chunks of two, which he rejected: it would have left
+the budget unused when the session window expired. His correction asked for an adaptive
+scheme, and that is what shipped.
+
+Each phase runs on a worker pool that fills to 16 immediately -- every worker takes its first
+item unconditionally -- and then gates only *replacements*. When a worker finishes an item
+it launches another only if
+
+```
+budget.remaining() >= ((inFlight + 1) + reserve) * estCostPerAgent * 1.2
+```
+
+otherwise it retires permanently, so concurrency tapers 16 -> ... -> 1 as the budget drains
+and the run ends with one agent in flight instead of sixteen. `estCostPerAgent` is
+`(budget.spent() - baselineAtStart) / liveCompletions`, with a 45k-output-token prior until
+three live agents have finished; `budget.spent()` is a cumulative session-shared counter, so
+it is baselined per invocation, and a completion counts as "live" only if its surrounding
+delta exceeds 2k output tokens, so cached replays on resume do not drag the estimate to zero.
+`reserve` holds back capacity for phases that still must run (8 agents through Hunt and the
+candidate chains, 7 through Coherence) so the taper starts early enough that the report still
+gets written. Retirement is logged, and a pool that stops with items unstarted logs
+`POOL STOPPED EARLY: n/m`; resuming the run ID replays completed agents from the
+content-addressed cache and runs only the remainder.
+
+**This run took the fallback branch the owner authorized.** `budget.remaining()` is finite
+only when a token target is set for the turn; none was, so it was `Infinity`, the gate always
+passed, and the run executed as a flat 16-wide pool. Measured 7 minutes into the Hunt phase:
+24 agent transcripts, 15 running concurrently. The taper code is present and engages
+whenever a target is set.
+
+## What the orchestrator did itself
+
+Outside the workflow, the orchestrating model: built the three trees and the harness; found
+four seed observations by hand and put them in the shared prompt so agents would extend
+rather than rediscover them; and independently checked two headline recon claims with
+explicit outside-CREATE-SCHEMA controls, confirming one and refuting the other
+(`csxrun/evidence/orch-verify1.txt`). It wrote this file. It did not write the report or
+any test.
+
+## Outcome
+
+* **83 subagents**, 0 errors, 8,252,907 subagent output tokens, 2,299 tool calls, 2h59m
+ wall clock (workflow run `wf_784c5f4f-e9b`, three invocations -- see
+ [Interruptions](#interruptions)).
+* 6 recon dossiers -> 16 hunt lenses -> **72 raw observations** against roughly 450 things
+ each lens checked and found correct -> **19 deduped candidates** -> 3 survived the
+ refute stage.
+* The report presents **4 defects attributable to d516974** (D1-D4), a separate section of
+ findings belonging to the sibling commit `a9c350d`, a section on what was examined and
+ found correct, and the 16 rejected candidates with the reason each was rejected.
+* The report writer reinstated narrowed forms of two candidates the refute stage had
+ killed (the `SET role` clause, D2, and tab completion, D4). It did so openly: D2 records
+ `is_new: true for this spelling, false for the rule` and carries an Attribution paragraph
+ saying the rule violation is reachable on `pre` by a harder route, and D4 carries a
+ "Counterarguments a reviewer should weigh" block. Both rest on `per_principles = true`,
+ which is sufficient under the rubric on its own. A reader who disagrees with that reading
+ should read those two blocks first.
+
+## Tests on this branch
+
+There is **no fix on this branch**, so the tests encode what a *correct* implementation
+would print and therefore **fail on master by design**. The failure diff is the
+demonstration. 339 lines were added across six test files; no non-test file was touched.
+
+Verified by the orchestrator, not by an agent:
+
+* With the additions: `regress/regress` (create_schema), `plpgsql/regress` (plpgsql_misc),
+ `psql/001_basic` (subtests 135-137) and `psql/010_tab_completion` (subtests 110-111) fail,
+ each only on the new material. Three further meson tests -- `pg_upgrade/002_pg_upgrade`,
+ `recovery/027_stream_regress`, `test_plan_advice/001_replan_regress` -- fail collaterally
+ because each runs the core `parallel_schedule`.
+* Each added block ships its own control, and every control passes: `001_basic` subtests
+ 138-140 (the same input with a non-routine clause splits correctly) and
+ `010_tab_completion` subtest 112 (the same completion works at top level).
+* With the six files stashed, all six suites are green: 243 + 13 + 134 + 101 + 2 + 5
+ subtests pass, 0 failures.
+
+Reproduce:
+
+```
+export TMPDIR=/home/nm/src/pg/csxrun/tmp
+meson test -C /home/nm/src/pg/csx-build --suite regress --suite plpgsql --suite psql \
+ --num-processes 1
+```
+
+## Interruptions
+
+The run was stopped and resumed twice. Completed agents replay from a content-addressed
+cache keyed on (prompt, opts), so each stop cost only the agents in flight; of 25 starts
+across the first two invocations only 2 keys were started twice.
+
+1. After the first two recon agents, to replace the rejected chunk-of-two scheduler with the
+ adaptive pool described above.
+2. After the hunt phase, on noticing that 24 candidates times 3 chain agents would exhaust
+ the agent cap before the test and report phases could run. The fix raised the cap to the
+ owner's stated 90, capped triage at 20 candidates, and added a guard that stops the chain
+ phase from taking a new candidate once `SPAWNED + 3` would cross `90 - 9`, permanently
+ reserving 9 agents for coherence, tests and the report.
+
+## A host condition that contaminates some agent transcripts
+
+`/tmp` on this machine is a **3.9 GB tmpfs**, and it reached 100% full (27 MB free) partway
+through the run, from unrelated work in other directories. `pgrun.sh` and `pgsh.sh`
+originally created their throwaway clusters under `/tmp`, so during that window `initdb`
+failed with
+
+```
+FATAL: could not write to file "base/4/2675": No space left on device
+```
+
+Any such line in an evidence transcript is that condition, **not** a PostgreSQL defect and
+not a finding. The harness was repointed to `/home/nm/src/pg/csxrun/tmp` (on `/`, which had
+22 GB free) and the affected checks were re-run. Nothing was deleted from `/tmp`. A
+reviewer re-running the suites should set `TMPDIR` to a disk-backed directory, because
+`pg_regress` puts its socket directory under `TMPDIR`.
+
+## What the orchestrator verified by hand
+
+Independently of the agents, and recorded in `csxrun/evidence/orch-verify*.txt`:
+
+* **D1 reproduced from scratch, with a control the agents had not run in that form.** On
+ `new`, `CREATE SCHEMA m CREATE FUNCTION f() ... GRANT ALL ON SCHEMA m TO begin;` followed
+ by `VACUUM;` yields `ERROR: VACUUM cannot run inside a transaction block` and the next
+ statement never runs, proving psql merged them into one message; on `old` the element is a
+ syntax error and splitting is correct; and replacing the routine clause with
+ `CREATE VIEW v AS SELECT 1` makes the identical GRANT split correctly.
+* **`SET search_path FROM CURRENT` confirmed** to capture the internal temporary path
+ (`sp1, "$user", public`) where the session value is `"$user", public`.
+* **One recon claim refuted**: that `CREATE SCHEMA` causes `pg_type.typdefault` to be stored
+ unqualified. A control -- create the schema and function first, `SET search_path = sm,
+ public`, then `CREATE DOMAIN sm.dm AS int DEFAULT dflt()` -- produces the identical split
+ outside `CREATE SCHEMA`, so it is pre-existing deparse behaviour. Three separate hunt
+ lenses had reported it; the pipeline dropped it.
+* **The four highest-severity rejections and the two adjudications that bypassed the refute
+ stage were read and checked**, and their reasoning holds.
+* **The test outcome above**, both with and without the additions.
diff --git a/src/bin/psql/t/001_basic.pl b/src/bin/psql/t/001_basic.pl
index 028df33..189f82c 100644
--- a/src/bin/psql/t/001_basic.pl
+++ b/src/bin/psql/t/001_basic.pl
@@ -577,4 +577,44 @@ psql_fails_like(
qr/wrong key/,
'\unrestrict does not do backquote expansion');
+#
+# AUDIT ADDITION: a user-visible defect of commit
+# d516974840f4059d331ae6057ede3e4edd3c6747 ("Support more object types within
+# CREATE SCHEMA."). The first case below records the behavior that a CORRECT
+# implementation would produce, so it FAILS against master; the failure is
+# the demonstration of the defect. The second case is a passing control.
+#
+# FINDING C03 (new in d516974). Correct behavior: psql ends a CREATE SCHEMA
+# statement at its semicolon, so the VACUUM and the SELECT that follow it are
+# separate queries and both run.
+# Currently FAILS: the CREATE FUNCTION clause arms psqlscan.l's BEGIN/END
+# counter, and psqlscan.l clears that per-clause state only on the token
+# "create" -- GRANT is the one CREATE SCHEMA clause not starting with CREATE.
+# So the view named "begin" in the GRANT clause is counted as the start of a
+# routine body, and psql sends all three statements as one query: the VACUUM
+# then fails inside the resulting implicit transaction block and takes the
+# CREATE SCHEMA down with it.
+psql_like(
+ $node,
+ qq{CREATE SCHEMA cs_gb
+ CREATE VIEW begin AS SELECT 1 AS one
+ CREATE FUNCTION cs_gb_f() RETURNS int LANGUAGE sql AS 'select 1'
+ GRANT SELECT ON begin TO public;
+VACUUM;
+SELECT 'split ok' AS result;},
+ qr/^split ok$/m,
+ 'CREATE SCHEMA ends at its semicolon');
+
+# Control: the same shape with no routine clause, which splits correctly.
+psql_like(
+ $node,
+ qq{CREATE SCHEMA cs_gc
+ CREATE VIEW begin2 AS SELECT 1 AS one
+ CREATE VIEW cs_gc_v AS SELECT 1
+ GRANT SELECT ON begin2 TO public;
+VACUUM;
+SELECT 'split ok' AS result;},
+ qr/^split ok$/m,
+ 'control: CREATE SCHEMA with no routine clause');
+
done_testing();
diff --git a/src/bin/psql/t/010_tab_completion.pl b/src/bin/psql/t/010_tab_completion.pl
index 64e27ef..099ac3d 100644
--- a/src/bin/psql/t/010_tab_completion.pl
+++ b/src/bin/psql/t/010_tab_completion.pl
@@ -126,6 +126,29 @@ sub clear_line
return;
}
+# Like check_completion(), but report a test failure rather than dying when
+# the expected completion does not happen. (check_completion() waits for its
+# pattern and dies on timeout, which would abort the rest of this file.) The
+# ^U and \r sent after the input also take the place of clear_query().
+sub check_completion_nofail
+{
+ my ($send, $pattern, $annotation) = @_;
+
+ # report test failures from caller location
+ local $Test::Builder::Level = $Test::Builder::Level + 1;
+
+ my $out = $h->query_until(qr/Query buffer reset.*postgres=# $/s,
+ $send . "\025\\r\n");
+ my $okay = ($out =~ $pattern);
+ ok($okay, $annotation);
+ # for debugging, log actual output if it didn't match
+ local $Data::Dumper::Terse = 1;
+ local $Data::Dumper::Useqq = 1;
+ diag 'Actual output was ' . Dumper($out) . "Did not match \"$pattern\"\n"
+ if !$okay;
+ return;
+}
+
# check basic command completion: SEL<tab> produces SELECT<space>
check_completion("SEL\t", qr/SELECT /, "complete SEL<tab> to SELECT");
@@ -460,6 +483,78 @@ check_completion("FR\t", qr/FROM /,
clear_query();
+#
+# AUDIT ADDITION: a user-visible defect of commit
+# d516974840f4059d331ae6057ede3e4edd3c6747 ("Support more object types within
+# CREATE SCHEMA."). The two check_completion_nofail() cases below record the
+# completions that a CORRECT implementation would offer, so they FAIL against
+# master; the failures are the demonstration of the defect. The four
+# check_completion() cases before them and the two after are passing controls.
+#
+
+# Two clause types that d516974 did convert to TailMatches(), so that their
+# completions fire inside CREATE SCHEMA.
+check_completion(
+ "CREATE SCHEMA s CREATE DOMAIN d A\t",
+ qr/CREATE DOMAIN d AS /,
+ "complete CREATE DOMAIN inside CREATE SCHEMA");
+
+clear_query();
+
+check_completion(
+ "CREATE SCHEMA s CREATE TEXT SEARCH CONF\t",
+ qr/TEXT SEARCH CONFIGURATION /,
+ "complete CREATE TEXT SEARCH inside CREATE SCHEMA");
+
+clear_query();
+
+# d516974 also added COLLATION and TYPE to the clause list psql offers after
+# CREATE SCHEMA ... CREATE, so psql agrees that both are legal there.
+check_completion(
+ "CREATE SCHEMA s CREATE COLL\t",
+ qr/CREATE SCHEMA s CREATE COLLATION /,
+ "complete CREATE SCHEMA ... CREATE COLL<tab> to COLLATION");
+
+clear_query();
+
+check_completion(
+ "CREATE SCHEMA s CREATE TY\t",
+ qr/CREATE SCHEMA s CREATE TYPE /,
+ "complete CREATE SCHEMA ... CREATE TY<tab> to TYPE");
+
+clear_query();
+
+# FINDING O3 (new in d516974). Correct behavior: having offered COLLATION and
+# TYPE as clauses just above, psql goes on completing them inside CREATE
+# SCHEMA the way it does at top level (the two controls after these).
+# Currently FAILS: those two rules still use anchored Matches()/HeadMatches(),
+# which cannot match once "CREATE SCHEMA s" precedes them; d516974 converted
+# only CREATE DOMAIN and CREATE TEXT SEARCH.
+check_completion_nofail(
+ "CREATE SCHEMA s CREATE COLLATION c FR\t",
+ qr/CREATE COLLATION c FROM /,
+ "complete CREATE COLLATION inside CREATE SCHEMA");
+
+check_completion_nofail(
+ "CREATE SCHEMA s CREATE TYPE t AS EN\t",
+ qr/CREATE TYPE t AS ENUM /,
+ "complete CREATE TYPE inside CREATE SCHEMA");
+
+# Controls: the same two completions at top level, which do work.
+check_completion(
+ "CREATE COLLATION c FR\t",
+ qr/CREATE COLLATION c FROM /,
+ "complete CREATE COLLATION at top level");
+
+clear_query();
+
+check_completion(
+ "CREATE TYPE t AS EN\t",
+ qr/CREATE TYPE t AS ENUM /,
+ "complete CREATE TYPE at top level");
+
+clear_query();
+
# send psql an explicit \q to shut it down, else pty won't close properly
$h->quit or die "psql returned $?";
diff --git a/src/pl/plpgsql/src/expected/plpgsql_misc.out b/src/pl/plpgsql/src/expected/plpgsql_misc.out
index ffb377f..87c4585 100644
--- a/src/pl/plpgsql/src/expected/plpgsql_misc.out
+++ b/src/pl/plpgsql/src/expected/plpgsql_misc.out
@@ -29,6 +29,41 @@ CREATE OR REPLACE PROCEDURE public.test2(IN x integer)
BEGIN ATOMIC
SELECT (x + 2);
END
+--
+-- AUDIT ADDITION: a user-visible defect of commit
+-- d516974840f4059d331ae6057ede3e4edd3c6747 ("Support more object types within
+-- CREATE SCHEMA."). Unlike every case above, this one records the output
+-- that a CORRECT implementation would produce, so it FAILS against master;
+-- the failure diff is the demonstration of the defect.
+--
+-- FINDING C14 (new in d516974). Correct behavior: a CREATE SCHEMA whose
+-- element is a routine with a BEGIN ATOMIC body is writable as a direct
+-- statement in PL/pgSQL, exactly like the same routine outside CREATE SCHEMA
+-- above, and creates misc_schema.test3().
+-- Currently FAILS: make_execsql_stmt() counts BEGIN/END only after a leading
+-- CREATE [OR REPLACE] {FUNCTION|PROCEDURE}, never after CREATE SCHEMA, so it
+-- ends the statement at the first semicolon inside the routine body. psql's
+-- copy of this heuristic learned about CREATE SCHEMA in d516974 and 049b742;
+-- pl_gram.y's copy did not.
+do
+$$
+ begin
+ create schema misc_schema
+ create function test3() returns int
+ begin atomic
+ select 3 + 3;
+ end;
+ end
+$$;
+\sf misc_schema.test3
+CREATE OR REPLACE FUNCTION misc_schema.test3()
+ RETURNS integer
+ LANGUAGE sql
+BEGIN ATOMIC
+ SELECT (3 + 3);
+END
+drop schema misc_schema cascade;
+NOTICE: drop cascades to function misc_schema.test3()
-- Test %TYPE and %ROWTYPE error cases
create table misc_table(f1 int);
do $$ declare x foo%type; begin end $$;
diff --git a/src/pl/plpgsql/src/sql/plpgsql_misc.sql b/src/pl/plpgsql/src/sql/plpgsql_misc.sql
index 0bc39fc..d11f9a1 100644
--- a/src/pl/plpgsql/src/sql/plpgsql_misc.sql
+++ b/src/pl/plpgsql/src/sql/plpgsql_misc.sql
@@ -21,6 +21,37 @@ $$;
\sf test1
\sf test2
+--
+-- AUDIT ADDITION: a user-visible defect of commit
+-- d516974840f4059d331ae6057ede3e4edd3c6747 ("Support more object types within
+-- CREATE SCHEMA."). Unlike every case above, this one records the output
+-- that a CORRECT implementation would produce, so it FAILS against master;
+-- the failure diff is the demonstration of the defect.
+--
+-- FINDING C14 (new in d516974). Correct behavior: a CREATE SCHEMA whose
+-- element is a routine with a BEGIN ATOMIC body is writable as a direct
+-- statement in PL/pgSQL, exactly like the same routine outside CREATE SCHEMA
+-- above, and creates misc_schema.test3().
+-- Currently FAILS: make_execsql_stmt() counts BEGIN/END only after a leading
+-- CREATE [OR REPLACE] {FUNCTION|PROCEDURE}, never after CREATE SCHEMA, so it
+-- ends the statement at the first semicolon inside the routine body. psql's
+-- copy of this heuristic learned about CREATE SCHEMA in d516974 and 049b742;
+-- pl_gram.y's copy did not.
+do
+$$
+ begin
+ create schema misc_schema
+ create function test3() returns int
+ begin atomic
+ select 3 + 3;
+ end;
+ end
+$$;
+
+\sf misc_schema.test3
+
+drop schema misc_schema cascade;
+
-- Test %TYPE and %ROWTYPE error cases
create table misc_table(f1 int);
diff --git a/src/test/regress/expected/create_schema.out b/src/test/regress/expected/create_schema.out
index b9ae4c4..344741a 100644
--- a/src/test/regress/expected/create_schema.out
+++ b/src/test/regress/expected/create_schema.out
@@ -322,5 +322,83 @@ 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)
+--
+-- AUDIT ADDITIONS: user-visible defects of commit
+-- d516974840f4059d331ae6057ede3e4edd3c6747 ("Support more object types within
+-- CREATE SCHEMA."). Unlike every case above, each case below records the
+-- output that a CORRECT implementation would produce, so each one FAILS
+-- against master; the failure diff is the demonstration of the defect.
+--
+-- FINDING O1 (new in d516974). Correct behavior: a SET clause for "role" or
+-- "session_authorization" in a CREATE FUNCTION element behaves just as it
+-- does outside CREATE SCHEMA, so both functions below are created in the new
+-- schema, owned by the AUTHORIZATION role, with the setting in proconfig.
+-- Currently FAILS: CreateSchemaCommand runs the elements with
+-- InLocalUserIdChange() true, so guc.c's GUC_NOT_WHILE_SEC_REST test rejects
+-- the SET clause, naming a security-definer function that is not there. Two
+-- call paths reach that test and both need dealing with: CreateFunction ->
+-- GUCArrayAdd -> validate_option_array_item, which only validates
+-- (changeVal = false) and is what fails today, and then ProcedureCreate ->
+-- ProcessGUCArray, which applies proconfig around the language validator. A
+-- fix must not simply let the value through: SetSessionAuthorization()
+-- asserts that the security restriction context is clear.
+CREATE SCHEMA regress_schema_setrole AUTHORIZATION regress_create_schema_role
+ CREATE FUNCTION cs_setrole() RETURNS int LANGUAGE sql
+ SET role = 'regress_create_schema_role' AS 'SELECT 1'
+ CREATE FUNCTION cs_setsess() RETURNS int LANGUAGE sql
+ SET session_authorization = 'regress_create_schema_role' AS 'SELECT 2';
+SELECT p.proname, pg_get_userbyid(p.proowner) AS owner, p.proconfig
+ FROM pg_proc p JOIN pg_namespace n ON n.oid = p.pronamespace
+ WHERE n.nspname = 'regress_schema_setrole' ORDER BY 1;
+ proname | owner | proconfig
+------------+----------------------------+----------------------------------------------------
+ cs_setrole | regress_create_schema_role | {role=regress_create_schema_role}
+ cs_setsess | regress_create_schema_role | {session_authorization=regress_create_schema_role}
+(2 rows)
+
+SET client_min_messages = warning;
+DROP SCHEMA regress_schema_setrole CASCADE;
+RESET client_min_messages;
+-- FINDING C08 (not new in d516974: the boundary is a9c350d, which made
+-- CREATE SCHEMA hand the new schema to its subcommands as a prepended
+-- search_path entry). Correct behavior: a schema named exactly "$user"
+-- receives the objects created by its own subcommands, like any other schema.
+-- Currently FAILS: "$user" in a search_path is a macro for the current role
+-- name, so the elements land in whatever the rest of the path resolves to,
+-- or, with an empty path, nowhere at all. Both halves run in an aborted
+-- transaction, so neither the buggy nor the fixed behavior can leave objects
+-- behind in another schema.
+BEGIN;
+CREATE SCHEMA "$user"
+ CREATE TABLE cs_dollar_tab (a int)
+ CREATE FUNCTION cs_dollar_func() RETURNS int LANGUAGE sql AS 'SELECT 1';
+SELECT relnamespace::regnamespace AS tab_schema FROM pg_class
+ WHERE relname = 'cs_dollar_tab';
+ tab_schema
+------------
+ "$user"
+(1 row)
+
+SELECT pronamespace::regnamespace AS func_schema FROM pg_proc
+ WHERE proname = 'cs_dollar_func';
+ func_schema
+-------------
+ "$user"
+(1 row)
+
+ROLLBACK;
+-- Again, with an empty search_path, where the statement today reports a
+-- search_path error for the very schema it just created.
+BEGIN;
+SET LOCAL search_path = '';
+CREATE SCHEMA "$user" CREATE TABLE cs_dollar_tab2 (a int);
+SELECT relnamespace::regnamespace AS tab2_schema FROM pg_class
+ WHERE relname = 'cs_dollar_tab2';
+ tab2_schema
+-------------
+ "$user"
+(1 row)
+
+ROLLBACK;
-- 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 526bb3c..cec7f77 100644
--- a/src/test/regress/sql/create_schema.sql
+++ b/src/test/regress/sql/create_schema.sql
@@ -177,5 +177,65 @@ CREATE SCHEMA regress_schema_misc
DROP SCHEMA regress_schema_misc CASCADE;
+--
+-- AUDIT ADDITIONS: user-visible defects of commit
+-- d516974840f4059d331ae6057ede3e4edd3c6747 ("Support more object types within
+-- CREATE SCHEMA."). Unlike every case above, each case below records the
+-- output that a CORRECT implementation would produce, so each one FAILS
+-- against master; the failure diff is the demonstration of the defect.
+--
+
+-- FINDING O1 (new in d516974). Correct behavior: a SET clause for "role" or
+-- "session_authorization" in a CREATE FUNCTION element behaves just as it
+-- does outside CREATE SCHEMA, so both functions below are created in the new
+-- schema, owned by the AUTHORIZATION role, with the setting in proconfig.
+-- Currently FAILS: CreateSchemaCommand runs the elements with
+-- InLocalUserIdChange() true, so guc.c's GUC_NOT_WHILE_SEC_REST test rejects
+-- the SET clause, naming a security-definer function that is not there. Two
+-- call paths reach that test and both need dealing with: CreateFunction ->
+-- GUCArrayAdd -> validate_option_array_item, which only validates
+-- (changeVal = false) and is what fails today, and then ProcedureCreate ->
+-- ProcessGUCArray, which applies proconfig around the language validator. A
+-- fix must not simply let the value through: SetSessionAuthorization()
+-- asserts that the security restriction context is clear.
+CREATE SCHEMA regress_schema_setrole AUTHORIZATION regress_create_schema_role
+ CREATE FUNCTION cs_setrole() RETURNS int LANGUAGE sql
+ SET role = 'regress_create_schema_role' AS 'SELECT 1'
+ CREATE FUNCTION cs_setsess() RETURNS int LANGUAGE sql
+ SET session_authorization = 'regress_create_schema_role' AS 'SELECT 2';
+SELECT p.proname, pg_get_userbyid(p.proowner) AS owner, p.proconfig
+ FROM pg_proc p JOIN pg_namespace n ON n.oid = p.pronamespace
+ WHERE n.nspname = 'regress_schema_setrole' ORDER BY 1;
+SET client_min_messages = warning;
+DROP SCHEMA regress_schema_setrole CASCADE;
+RESET client_min_messages;
+
+-- FINDING C08 (not new in d516974: the boundary is a9c350d, which made
+-- CREATE SCHEMA hand the new schema to its subcommands as a prepended
+-- search_path entry). Correct behavior: a schema named exactly "$user"
+-- receives the objects created by its own subcommands, like any other schema.
+-- Currently FAILS: "$user" in a search_path is a macro for the current role
+-- name, so the elements land in whatever the rest of the path resolves to,
+-- or, with an empty path, nowhere at all. Both halves run in an aborted
+-- transaction, so neither the buggy nor the fixed behavior can leave objects
+-- behind in another schema.
+BEGIN;
+CREATE SCHEMA "$user"
+ CREATE TABLE cs_dollar_tab (a int)
+ CREATE FUNCTION cs_dollar_func() RETURNS int LANGUAGE sql AS 'SELECT 1';
+SELECT relnamespace::regnamespace AS tab_schema FROM pg_class
+ WHERE relname = 'cs_dollar_tab';
+SELECT pronamespace::regnamespace AS func_schema FROM pg_proc
+ WHERE proname = 'cs_dollar_func';
+ROLLBACK;
+-- Again, with an empty search_path, where the statement today reports a
+-- search_path error for the very schema it just created.
+BEGIN;
+SET LOCAL search_path = '';
+CREATE SCHEMA "$user" CREATE TABLE cs_dollar_tab2 (a int);
+SELECT relnamespace::regnamespace AS tab2_schema FROM pg_class
+ WHERE relname = 'cs_dollar_tab2';
+ROLLBACK;
+
-- Clean up
DROP ROLE regress_create_schema_role;
^ permalink raw reply [nested|flat] 55+ messages in thread
* Re: CREATE SCHEMA ... CREATE DOMAIN support
2024-11-12 12:54 CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-23 05:19 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-27 03:42 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-27 06:38 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-27 18:39 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-11-28 05:27 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-28 05:52 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-11-28 09:58 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-29 13:47 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-30 19:08 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-12-03 19:35 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Peter Eisentraut <peter@eisentraut.org>
2024-12-03 20:07 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-12-11 06:22 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-12-11 17:08 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2025-08-19 03:36 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2025-08-22 08:59 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2025-09-02 21:24 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2025-12-17 13:29 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2026-04-03 18:35 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2026-04-05 18:42 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2026-04-05 20:15 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2026-04-06 02:29 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2026-04-06 03:04 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2026-04-06 19:19 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2026-09-04 00:29 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Noah Misch <noah@leadboat.com>
@ 2026-09-04 01:00 ` Tom Lane <tgl@sss.pgh.pa.us>
2026-09-04 03:51 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Noah Misch <noah@leadboat.com>
0 siblings, 1 reply; 55+ messages in thread
From: Tom Lane @ 2026-09-04 01:00 UTC (permalink / raw)
To: Noah Misch <noah@leadboat.com>; +Cc: jian he <jian.universality@gmail.com>; Kirill Reshke <reshkekirill@gmail.com>; Peter Eisentraut <peter@eisentraut.org>; PostgreSQL Hackers <pgsql-hackers@lists.postgresql.org>
Noah Misch <noah@leadboat.com> writes:
> I had Opus 5 review d516974 "Support more object types within CREATE SCHEMA".
Thanks for doing that.
> Here's one that resonates more with me, adapted slightly from the a9c350d
> commit message:
> CREATE TABLE public.mytable (c int);
> CREATE SCHEMA myschema
> CREATE VIEW myview AS SELECT * FROM mytable
> CREATE TABLE mytable (c int);
> SET search_path = pg_catalog;
> \d+ myschema.myview
> RESET search_path;
> In v18, myview binds to myschema.mytable. In v19, it binds to public.mytable.
> I think the above-described syntax rule about <local or schema qualified name>
> doesn't allow v19's interpretation.
This is not about that, it is about a9c350d's specific intent "Don't
try to re-order the subcommands of CREATE SCHEMA". That is, we
rejected the idea of allowing forward references between CREATE SCHEMA
subcommands, therefore myview will not bind to a CREATE TABLE
subcommand that comes after it. Frankly, I don't care about arguments
that this is not what the spec says: I think this behavior is more
useful, more intelligible, and less bug-prone than the spec's
semantics. Moreover we were never going to be able to do what the spec
says, as discussed in the thread leading up to that commit.
> Stepping back, I am concerned about the complexity v19 is incurring to extend
> CREATE SCHEMA ... CREATE. The syntax gives only modest benefits, since you
> can achieve the same object creation with freestanding SQL statements. The
> above example is indicative of the unusual semantic challenges. CREATE SCHEMA
> ... CREATE also handicaps the parsing of every command it supports, since
> parsing can't rely on semicolons to find command boundaries.
True, but the spec is constrained by that too, so they won't be
inventing syntax that breaks the ability to do it. We were somewhat
fortunate that we'd not yet invented any extension syntax that would
break that ability, but we haven't, and now bison will tell us if
we try.
> ... this is a variant of an older bug of psql detecting "begin" in the
> wrong contexts:
> -- psql wrongly concludes command is not yet terminated
> CREATE FUNCTION x1f() RETURNS int LANGUAGE sql SET search_path = begin AS 'select 1';
> select 'ended';
Yeah. TBH, I think the right answer to that is to make BEGIN a
reserved word. It is so in the standard, and has been since SQL92.
If we don't, there are probably always going to be ways to confuse
psql like this (and I rather imagine that many of them were reachable
before v19).
>> +### D2 — a `SET role` / `SET session_authorization` clause on a routine element is refused under `AUTHORIZATION`, naming a security-definer function that does not exist
> I would not act on this.
Hmm, that one seems like a genuine bug to me; why do you think it
shouldn't be acted on?
>> +### D3 — PL/pgSQL cannot contain a `CREATE SCHEMA` whose element is a `BEGIN ATOMIC` routine
> I think this does qualify as a bug in d516974, but it's not very concerning.
That probably has something to do with the fact that BEGIN *is* a
reserved word to plpgsql. I agree it's not high priority, but
maybe it's worth looking at.
>> +### D4 — psql offers `COLLATION` and `TYPE` as `CREATE SCHEMA` clauses but does not complete them
> I would not act on this.
Agreed; we don't have particularly useful completion for those
commands at outer level, either.
regards, tom lane
^ permalink raw reply [nested|flat] 55+ messages in thread
* Re: CREATE SCHEMA ... CREATE DOMAIN support
2024-11-12 12:54 CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-23 05:19 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-27 03:42 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-27 06:38 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-27 18:39 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-11-28 05:27 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-28 05:52 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-11-28 09:58 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-29 13:47 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-30 19:08 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-12-03 19:35 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Peter Eisentraut <peter@eisentraut.org>
2024-12-03 20:07 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-12-11 06:22 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-12-11 17:08 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2025-08-19 03:36 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2025-08-22 08:59 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2025-09-02 21:24 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2025-12-17 13:29 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2026-04-03 18:35 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2026-04-05 18:42 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2026-04-05 20:15 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2026-04-06 02:29 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2026-04-06 03:04 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2026-04-06 19:19 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2026-09-04 00:29 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Noah Misch <noah@leadboat.com>
2026-09-04 01:00 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
@ 2026-09-04 03:51 ` Noah Misch <noah@leadboat.com>
2026-09-04 14:21 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
0 siblings, 1 reply; 55+ messages in thread
From: Noah Misch @ 2026-09-04 03:51 UTC (permalink / raw)
To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: jian he <jian.universality@gmail.com>; Kirill Reshke <reshkekirill@gmail.com>; Peter Eisentraut <peter@eisentraut.org>; PostgreSQL Hackers <pgsql-hackers@lists.postgresql.org>
On Thu, Sep 03, 2026 at 09:00:26PM -0400, Tom Lane wrote:
> Noah Misch <noah@leadboat.com> writes:
> > CREATE TABLE public.mytable (c int);
> > CREATE SCHEMA myschema
> > CREATE VIEW myview AS SELECT * FROM mytable
> > CREATE TABLE mytable (c int);
> > SET search_path = pg_catalog;
> > \d+ myschema.myview
> > RESET search_path;
>
> > In v18, myview binds to myschema.mytable. In v19, it binds to public.mytable.
> > I think the above-described syntax rule about <local or schema qualified name>
> > doesn't allow v19's interpretation.
>
> This is not about that, it is about a9c350d's specific intent "Don't
> try to re-order the subcommands of CREATE SCHEMA". That is, we
> rejected the idea of allowing forward references between CREATE SCHEMA
> subcommands, therefore myview will not bind to a CREATE TABLE
> subcommand that comes after it. Frankly, I don't care about arguments
> that this is not what the spec says: I think this behavior is more
> useful, more intelligible, and less bug-prone than the spec's
> semantics. Moreover we were never going to be able to do what the spec
> says, as discussed in the thread leading up to that commit.
I'm not saying that I want the server to reorder the commands or that I know
of a standard requiring reorder. I'm fine requiring the user to order things
for us. The syntax rule I cited[1] appears to require my original example to
have the same name resolution as this variant w/ more schema qualifications:
CREATE TABLE public.mytable (c int);
CREATE SCHEMA myschema
CREATE VIEW myschema.myview AS SELECT * FROM myschema.mytable
CREATE TABLE myschema.mytable (c int);
SET search_path = pg_catalog;
\d+ myschema.myview
RESET search_path;
That succeeds in v18 and fails in v19, which is fine. The problem is that my
original example (with less schema qualification) succeeds in both versions
with different meaning, and the v19 meaning is not standard-compliant.
[1] "If the <local or schema qualified name> is contained in a <schema
definition>, then the <schema name> that is specified or implicit in the
<schema definition> is implicit."
> >> +### D2 — a `SET role` / `SET session_authorization` clause on a routine element is refused under `AUTHORIZATION`, naming a security-definer function that does not exist
>
> > I would not act on this.
>
> Hmm, that one seems like a genuine bug to me; why do you think it
> shouldn't be acted on?
It's genuine, but the combination involved is rare enough that I'd shelve it
so long as we're not in a post-scarcity world. No objection if someone wants
to pursue it.
^ permalink raw reply [nested|flat] 55+ messages in thread
* Re: CREATE SCHEMA ... CREATE DOMAIN support
2024-11-12 12:54 CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-23 05:19 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-27 03:42 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-27 06:38 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-27 18:39 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-11-28 05:27 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-28 05:52 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-11-28 09:58 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-29 13:47 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-30 19:08 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-12-03 19:35 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Peter Eisentraut <peter@eisentraut.org>
2024-12-03 20:07 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-12-11 06:22 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-12-11 17:08 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2025-08-19 03:36 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2025-08-22 08:59 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2025-09-02 21:24 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2025-12-17 13:29 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2026-04-03 18:35 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2026-04-05 18:42 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2026-04-05 20:15 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2026-04-06 02:29 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2026-04-06 03:04 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2026-04-06 19:19 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2026-09-04 00:29 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Noah Misch <noah@leadboat.com>
2026-09-04 01:00 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2026-09-04 03:51 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Noah Misch <noah@leadboat.com>
@ 2026-09-04 14:21 ` Tom Lane <tgl@sss.pgh.pa.us>
2026-09-04 18:07 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Noah Misch <noah@leadboat.com>
0 siblings, 1 reply; 55+ messages in thread
From: Tom Lane @ 2026-09-04 14:21 UTC (permalink / raw)
To: Noah Misch <noah@leadboat.com>; +Cc: jian he <jian.universality@gmail.com>; Kirill Reshke <reshkekirill@gmail.com>; Peter Eisentraut <peter@eisentraut.org>; PostgreSQL Hackers <pgsql-hackers@lists.postgresql.org>
Noah Misch <noah@leadboat.com> writes:
> On Thu, Sep 03, 2026 at 09:00:26PM -0400, Tom Lane wrote:
>> This is not about that, it is about a9c350d's specific intent "Don't
>> try to re-order the subcommands of CREATE SCHEMA". That is, we
>> rejected the idea of allowing forward references between CREATE SCHEMA
>> subcommands, therefore myview will not bind to a CREATE TABLE
>> subcommand that comes after it.
> I'm not saying that I want the server to reorder the commands or that I know
> of a standard requiring reorder. I'm fine requiring the user to order things
> for us. The syntax rule I cited[1] appears to require my original example to
> have the same name resolution as this variant w/ more schema qualifications:
> CREATE TABLE public.mytable (c int);
> CREATE SCHEMA myschema
> CREATE VIEW myschema.myview AS SELECT * FROM myschema.mytable
> CREATE TABLE myschema.mytable (c int);
> SET search_path = pg_catalog;
> \d+ myschema.myview
> RESET search_path;
> That succeeds in v18 and fails in v19, which is fine. The problem is that my
> original example (with less schema qualification) succeeds in both versions
> with different meaning, and the v19 meaning is not standard-compliant.
What would you have us do differently?
As near as I can tell, the spec's wording is untenable anyway: it
appears to say that names within a CREATE SCHEMA can never refer to
objects outside the new schema unless explicitly schema-qualified.
Half a moment's thought shows that that's unworkable for Postgres:
CREATE SCHEMA s1
CREATE TABLE t1 (f1 int)
CREATE VIEW v1 AS SELECT abs(f1) AS a, f1 + 1 AS b FROM t1;
With that interpretation, both this reference to pg_catalog.abs()
and this reference to operator(pg_catalog.+) must fail. I don't
think anyone's going to consider that behavior better. But as
soon as you admit that "oh, pg_catalog must be implicitly in the
search path too", you might as well admit that our existing behavior
where the whole search_path is implicitly available is okay.
>> Hmm, that one seems like a genuine bug to me; why do you think it
>> shouldn't be acted on?
> It's genuine, but the combination involved is rare enough that I'd shelve it
> so long as we're not in a post-scarcity world. No objection if someone wants
> to pursue it.
Fair enough. I'll try to make some time for it, but not urgently.
regards, tom lane
^ permalink raw reply [nested|flat] 55+ messages in thread
* Re: CREATE SCHEMA ... CREATE DOMAIN support
2024-11-12 12:54 CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-23 05:19 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-27 03:42 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-27 06:38 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-27 18:39 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-11-28 05:27 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-28 05:52 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-11-28 09:58 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-29 13:47 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-30 19:08 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-12-03 19:35 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Peter Eisentraut <peter@eisentraut.org>
2024-12-03 20:07 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-12-11 06:22 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-12-11 17:08 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2025-08-19 03:36 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2025-08-22 08:59 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2025-09-02 21:24 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2025-12-17 13:29 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2026-04-03 18:35 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2026-04-05 18:42 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2026-04-05 20:15 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2026-04-06 02:29 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2026-04-06 03:04 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2026-04-06 19:19 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2026-09-04 00:29 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Noah Misch <noah@leadboat.com>
2026-09-04 01:00 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2026-09-04 03:51 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Noah Misch <noah@leadboat.com>
2026-09-04 14:21 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
@ 2026-09-04 18:07 ` Noah Misch <noah@leadboat.com>
2026-09-04 19:30 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
0 siblings, 1 reply; 55+ messages in thread
From: Noah Misch @ 2026-09-04 18:07 UTC (permalink / raw)
To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: jian he <jian.universality@gmail.com>; Kirill Reshke <reshkekirill@gmail.com>; Peter Eisentraut <peter@eisentraut.org>; PostgreSQL Hackers <pgsql-hackers@lists.postgresql.org>
On Fri, Sep 04, 2026 at 10:21:52AM -0400, Tom Lane wrote:
> Noah Misch <noah@leadboat.com> writes:
> > On Thu, Sep 03, 2026 at 09:00:26PM -0400, Tom Lane wrote:
> >> This is not about that, it is about a9c350d's specific intent "Don't
> >> try to re-order the subcommands of CREATE SCHEMA". That is, we
> >> rejected the idea of allowing forward references between CREATE SCHEMA
> >> subcommands, therefore myview will not bind to a CREATE TABLE
> >> subcommand that comes after it.
>
> > I'm not saying that I want the server to reorder the commands or that I know
> > of a standard requiring reorder. I'm fine requiring the user to order things
> > for us. The syntax rule I cited[1] appears to require my original example to
> > have the same name resolution as this variant w/ more schema qualifications:
>
> > CREATE TABLE public.mytable (c int);
> > CREATE SCHEMA myschema
> > CREATE VIEW myschema.myview AS SELECT * FROM myschema.mytable
> > CREATE TABLE myschema.mytable (c int);
> > SET search_path = pg_catalog;
> > \d+ myschema.myview
> > RESET search_path;
>
> > That succeeds in v18 and fails in v19, which is fine. The problem is that my
> > original example (with less schema qualification) succeeds in both versions
> > with different meaning, and the v19 meaning is not standard-compliant.
>
> What would you have us do differently?
I recommend reverting the v19 CREATE SCHEMA changes, unfortunately. The
thread started with what smelled like low-hanging fruit: let users write
"CREATE SCHEMA myschema CREATE DOMAIN mydomain" instead of "CREATE SCHEMA
myschema; CREATE DOMAIN myschema.mydomain". That led to a9c350d silently
changing the meaning of CREATE SCHEMA statements that v18 already accepts.
Since the benefit of supporting "CREATE SCHEMA myschema CREATE DOMAIN
mydomain" is so modest, it doesn't take much of a problem to outweigh that
benefit.
> As near as I can tell, the spec's wording is untenable anyway: it
> appears to say that names within a CREATE SCHEMA can never refer to
> objects outside the new schema unless explicitly schema-qualified.
> Half a moment's thought shows that that's unworkable for Postgres:
>
> CREATE SCHEMA s1
> CREATE TABLE t1 (f1 int)
> CREATE VIEW v1 AS SELECT abs(f1) AS a, f1 + 1 AS b FROM t1;
>
> With that interpretation, both this reference to pg_catalog.abs()
> and this reference to operator(pg_catalog.+) must fail. I don't
> think anyone's going to consider that behavior better. But as
> soon as you admit that "oh, pg_catalog must be implicitly in the
> search path too", you might as well admit that our existing behavior
> where the whole search_path is implicitly available is okay.
It's a tarpit for sure. I don't think the benefit of supporting more object
types in CREATE SCHEMA warrants sorting all of that out. Let's just keep
writing multiple SQL statements.
^ permalink raw reply [nested|flat] 55+ messages in thread
* Re: CREATE SCHEMA ... CREATE DOMAIN support
2024-11-12 12:54 CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-23 05:19 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-27 03:42 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-27 06:38 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-27 18:39 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-11-28 05:27 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-28 05:52 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-11-28 09:58 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-29 13:47 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-30 19:08 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-12-03 19:35 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Peter Eisentraut <peter@eisentraut.org>
2024-12-03 20:07 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-12-11 06:22 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-12-11 17:08 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2025-08-19 03:36 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2025-08-22 08:59 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2025-09-02 21:24 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2025-12-17 13:29 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2026-04-03 18:35 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2026-04-05 18:42 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2026-04-05 20:15 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2026-04-06 02:29 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2026-04-06 03:04 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2026-04-06 19:19 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2026-09-04 00:29 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Noah Misch <noah@leadboat.com>
2026-09-04 01:00 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2026-09-04 03:51 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Noah Misch <noah@leadboat.com>
2026-09-04 14:21 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2026-09-04 18:07 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Noah Misch <noah@leadboat.com>
@ 2026-09-04 19:30 ` Tom Lane <tgl@sss.pgh.pa.us>
2026-09-09 15:53 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Heikki Linnakangas <hlinnaka@iki.fi>
0 siblings, 1 reply; 55+ messages in thread
From: Tom Lane @ 2026-09-04 19:30 UTC (permalink / raw)
To: Noah Misch <noah@leadboat.com>; +Cc: jian he <jian.universality@gmail.com>; Kirill Reshke <reshkekirill@gmail.com>; Peter Eisentraut <peter@eisentraut.org>; PostgreSQL Hackers <pgsql-hackers@lists.postgresql.org>
Noah Misch <noah@leadboat.com> writes:
> On Fri, Sep 04, 2026 at 10:21:52AM -0400, Tom Lane wrote:
>> What would you have us do differently?
> I recommend reverting the v19 CREATE SCHEMA changes, unfortunately. The
> thread started with what smelled like low-hanging fruit: let users write
> "CREATE SCHEMA myschema CREATE DOMAIN mydomain" instead of "CREATE SCHEMA
> myschema; CREATE DOMAIN myschema.mydomain". That led to a9c350d silently
> changing the meaning of CREATE SCHEMA statements that v18 already accepts.
> Since the benefit of supporting "CREATE SCHEMA myschema CREATE DOMAIN
> mydomain" is so modest, it doesn't take much of a problem to outweigh that
> benefit.
Well, you have a point. There's pretty nearly nothing that's
well-designed about CREATE SCHEMA subcommands: as you say, the syntax
is shaky, and the namespace visible to subcommands is unclear, and
the whole business of re-ordering subcommands is debatable, and even
if all those things were fixed the benefit of the entire feature is
marginal. So maybe we should just say that our legacy implementation
is what it is and we will mark it deprecated rather than putting any
effort into improving it. I'd like to see more than one vote in favor
of that before I do it, though.
regards, tom lane
^ permalink raw reply [nested|flat] 55+ messages in thread
* Re: CREATE SCHEMA ... CREATE DOMAIN support
2024-11-12 12:54 CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-23 05:19 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-27 03:42 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-27 06:38 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-27 18:39 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-11-28 05:27 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-28 05:52 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-11-28 09:58 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-29 13:47 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-30 19:08 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-12-03 19:35 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Peter Eisentraut <peter@eisentraut.org>
2024-12-03 20:07 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-12-11 06:22 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-12-11 17:08 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2025-08-19 03:36 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2025-08-22 08:59 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2025-09-02 21:24 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2025-12-17 13:29 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2026-04-03 18:35 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2026-04-05 18:42 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2026-04-05 20:15 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2026-04-06 02:29 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2026-04-06 03:04 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2026-04-06 19:19 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2026-09-04 00:29 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Noah Misch <noah@leadboat.com>
2026-09-04 01:00 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2026-09-04 03:51 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Noah Misch <noah@leadboat.com>
2026-09-04 14:21 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2026-09-04 18:07 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Noah Misch <noah@leadboat.com>
2026-09-04 19:30 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
@ 2026-09-09 15:53 ` Heikki Linnakangas <hlinnaka@iki.fi>
2026-09-09 16:17 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Nathan Bossart <nathandbossart@gmail.com>
0 siblings, 1 reply; 55+ messages in thread
From: Heikki Linnakangas @ 2026-09-09 15:53 UTC (permalink / raw)
To: Tom Lane <tgl@sss.pgh.pa.us>; Noah Misch <noah@leadboat.com>; +Cc: jian he <jian.universality@gmail.com>; Kirill Reshke <reshkekirill@gmail.com>; Peter Eisentraut <peter@eisentraut.org>; PostgreSQL Hackers <pgsql-hackers@lists.postgresql.org>
On 04/09/2026 22:30, Tom Lane wrote:
> Noah Misch <noah@leadboat.com> writes:
>> On Fri, Sep 04, 2026 at 10:21:52AM -0400, Tom Lane wrote:
>>> What would you have us do differently?
>
>> I recommend reverting the v19 CREATE SCHEMA changes, unfortunately. The
>> thread started with what smelled like low-hanging fruit: let users write
>> "CREATE SCHEMA myschema CREATE DOMAIN mydomain" instead of "CREATE SCHEMA
>> myschema; CREATE DOMAIN myschema.mydomain". That led to a9c350d silently
>> changing the meaning of CREATE SCHEMA statements that v18 already accepts.
>> Since the benefit of supporting "CREATE SCHEMA myschema CREATE DOMAIN
>> mydomain" is so modest, it doesn't take much of a problem to outweigh that
>> benefit.
>
> Well, you have a point. There's pretty nearly nothing that's
> well-designed about CREATE SCHEMA subcommands: as you say, the syntax
> is shaky, and the namespace visible to subcommands is unclear, and
> the whole business of re-ordering subcommands is debatable, and even
> if all those things were fixed the benefit of the entire feature is
> marginal. So maybe we should just say that our legacy implementation
> is what it is and we will mark it deprecated rather than putting any
> effort into improving it. I'd like to see more than one vote in favor
> of that before I do it, though.
Reverting seems like the safest thing to do. This is a really niche
thing, but whatever behavior we pick, we'll be stuck with it for a long
time. If we're not 100% certain about the new behavior, better to take a
step back and discuss and design it with more time.
- Heikki
^ permalink raw reply [nested|flat] 55+ messages in thread
* Re: CREATE SCHEMA ... CREATE DOMAIN support
2024-11-12 12:54 CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-23 05:19 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-27 03:42 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-27 06:38 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-27 18:39 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-11-28 05:27 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-28 05:52 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-11-28 09:58 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-29 13:47 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-30 19:08 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-12-03 19:35 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Peter Eisentraut <peter@eisentraut.org>
2024-12-03 20:07 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-12-11 06:22 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-12-11 17:08 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2025-08-19 03:36 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2025-08-22 08:59 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2025-09-02 21:24 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2025-12-17 13:29 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2026-04-03 18:35 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2026-04-05 18:42 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2026-04-05 20:15 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2026-04-06 02:29 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2026-04-06 03:04 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2026-04-06 19:19 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2026-09-04 00:29 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Noah Misch <noah@leadboat.com>
2026-09-04 01:00 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2026-09-04 03:51 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Noah Misch <noah@leadboat.com>
2026-09-04 14:21 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2026-09-04 18:07 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Noah Misch <noah@leadboat.com>
2026-09-04 19:30 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2026-09-09 15:53 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Heikki Linnakangas <hlinnaka@iki.fi>
@ 2026-09-09 16:17 ` Nathan Bossart <nathandbossart@gmail.com>
2026-09-09 16:44 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
0 siblings, 1 reply; 55+ messages in thread
From: Nathan Bossart @ 2026-09-09 16:17 UTC (permalink / raw)
To: Heikki Linnakangas <hlinnaka@iki.fi>; +Cc: Tom Lane <tgl@sss.pgh.pa.us>; Noah Misch <noah@leadboat.com>; jian he <jian.universality@gmail.com>; Kirill Reshke <reshkekirill@gmail.com>; Peter Eisentraut <peter@eisentraut.org>; PostgreSQL Hackers <pgsql-hackers@lists.postgresql.org>
On Wed, Sep 09, 2026 at 06:53:58PM +0300, Heikki Linnakangas wrote:
> On 04/09/2026 22:30, Tom Lane wrote:
>> Well, you have a point. There's pretty nearly nothing that's
>> well-designed about CREATE SCHEMA subcommands: as you say, the syntax
>> is shaky, and the namespace visible to subcommands is unclear, and
>> the whole business of re-ordering subcommands is debatable, and even
>> if all those things were fixed the benefit of the entire feature is
>> marginal. So maybe we should just say that our legacy implementation
>> is what it is and we will mark it deprecated rather than putting any
>> effort into improving it. I'd like to see more than one vote in favor
>> of that before I do it, though.
>
> Reverting seems like the safest thing to do. This is a really niche thing,
> but whatever behavior we pick, we'll be stuck with it for a long time. If
> we're not 100% certain about the new behavior, better to take a step back
> and discuss and design it with more time.
+1
--
nathan
^ permalink raw reply [nested|flat] 55+ messages in thread
* Re: CREATE SCHEMA ... CREATE DOMAIN support
2024-11-12 12:54 CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-23 05:19 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-27 03:42 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-27 06:38 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-27 18:39 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-11-28 05:27 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-28 05:52 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-11-28 09:58 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-29 13:47 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-30 19:08 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-12-03 19:35 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Peter Eisentraut <peter@eisentraut.org>
2024-12-03 20:07 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-12-11 06:22 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-12-11 17:08 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2025-08-19 03:36 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2025-08-22 08:59 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2025-09-02 21:24 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2025-12-17 13:29 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2026-04-03 18:35 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2026-04-05 18:42 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2026-04-05 20:15 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2026-04-06 02:29 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2026-04-06 03:04 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2026-04-06 19:19 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2026-09-04 00:29 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Noah Misch <noah@leadboat.com>
2026-09-04 01:00 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2026-09-04 03:51 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Noah Misch <noah@leadboat.com>
2026-09-04 14:21 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2026-09-04 18:07 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Noah Misch <noah@leadboat.com>
2026-09-04 19:30 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2026-09-09 15:53 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Heikki Linnakangas <hlinnaka@iki.fi>
2026-09-09 16:17 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Nathan Bossart <nathandbossart@gmail.com>
@ 2026-09-09 16:44 ` Tom Lane <tgl@sss.pgh.pa.us>
2026-09-09 18:02 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Noah Misch <noah@leadboat.com>
0 siblings, 1 reply; 55+ messages in thread
From: Tom Lane @ 2026-09-09 16:44 UTC (permalink / raw)
To: Nathan Bossart <nathandbossart@gmail.com>; +Cc: Heikki Linnakangas <hlinnaka@iki.fi>; Noah Misch <noah@leadboat.com>; jian he <jian.universality@gmail.com>; Kirill Reshke <reshkekirill@gmail.com>; Peter Eisentraut <peter@eisentraut.org>; PostgreSQL Hackers <pgsql-hackers@lists.postgresql.org>
Nathan Bossart <nathandbossart@gmail.com> writes:
> On Wed, Sep 09, 2026 at 06:53:58PM +0300, Heikki Linnakangas wrote:
>> Reverting seems like the safest thing to do. This is a really niche thing,
>> but whatever behavior we pick, we'll be stuck with it for a long time. If
>> we're not 100% certain about the new behavior, better to take a step back
>> and discuss and design it with more time.
> +1
After looking at the git history a bit, the relevant commits are
Branch: master Release: REL_19_BR [049b742da] 2026-06-23 14:12:03 -0400
psql: Tighten heuristics for BEGIN/END within CREATE SCHEMA.
Branch: master Release: REL_19_BR [d51697484] 2026-04-06 15:16:25 -0400
Support more object types within CREATE SCHEMA.
Branch: master Release: REL_19_BR [404db8f9e] 2026-04-06 15:16:25 -0400
Execute foreign key constraints in CREATE SCHEMA at the end.
Branch: master Release: REL_19_BR [a9c350d9e] 2026-04-06 15:16:25 -0400
Don't try to re-order the subcommands of CREATE SCHEMA.
It looks like it might be possible to revert a9c350d9e and d51697484
while keeping 404db8f9e, which would preserve a feature clearly
required by the SQL standard (F311-01, "Support for circular
references in that <referential constraint definition>s in two
different <table definition>s may reference columns in the other
table") while not breaking compatibility with our pre-19 behavior.
Any opinions about whether that's a good thing to try to do, rather
than a wholesale revert?
I'm also inclined to keep this bit of 049b742da:
This patch also fixes what seems to me (tgl) a small bug: \;
would reset BEGIN/END detection even when inside parens or BEGIN.
That's unlike what a plain semicolon would do, and no such effect
is suggested by the documentation.
regards, tom lane
^ permalink raw reply [nested|flat] 55+ messages in thread
* Re: CREATE SCHEMA ... CREATE DOMAIN support
2024-11-12 12:54 CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-23 05:19 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-27 03:42 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-27 06:38 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-27 18:39 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-11-28 05:27 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-28 05:52 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-11-28 09:58 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-29 13:47 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-30 19:08 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-12-03 19:35 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Peter Eisentraut <peter@eisentraut.org>
2024-12-03 20:07 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-12-11 06:22 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-12-11 17:08 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2025-08-19 03:36 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2025-08-22 08:59 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2025-09-02 21:24 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2025-12-17 13:29 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2026-04-03 18:35 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2026-04-05 18:42 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2026-04-05 20:15 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2026-04-06 02:29 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2026-04-06 03:04 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2026-04-06 19:19 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2026-09-04 00:29 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Noah Misch <noah@leadboat.com>
2026-09-04 01:00 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2026-09-04 03:51 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Noah Misch <noah@leadboat.com>
2026-09-04 14:21 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2026-09-04 18:07 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Noah Misch <noah@leadboat.com>
2026-09-04 19:30 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2026-09-09 15:53 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Heikki Linnakangas <hlinnaka@iki.fi>
2026-09-09 16:17 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Nathan Bossart <nathandbossart@gmail.com>
2026-09-09 16:44 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
@ 2026-09-09 18:02 ` Noah Misch <noah@leadboat.com>
2026-09-09 18:28 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
0 siblings, 1 reply; 55+ messages in thread
From: Noah Misch @ 2026-09-09 18:02 UTC (permalink / raw)
To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Nathan Bossart <nathandbossart@gmail.com>; Heikki Linnakangas <hlinnaka@iki.fi>; jian he <jian.universality@gmail.com>; Kirill Reshke <reshkekirill@gmail.com>; Peter Eisentraut <peter@eisentraut.org>; PostgreSQL Hackers <pgsql-hackers@lists.postgresql.org>
On Wed, Sep 09, 2026 at 12:44:51PM -0400, Tom Lane wrote:
> Nathan Bossart <nathandbossart@gmail.com> writes:
> > On Wed, Sep 09, 2026 at 06:53:58PM +0300, Heikki Linnakangas wrote:
> >> Reverting seems like the safest thing to do. This is a really niche thing,
> >> but whatever behavior we pick, we'll be stuck with it for a long time. If
> >> we're not 100% certain about the new behavior, better to take a step back
> >> and discuss and design it with more time.
>
> > +1
>
> After looking at the git history a bit, the relevant commits are
>
> Branch: master Release: REL_19_BR [049b742da] 2026-06-23 14:12:03 -0400
> psql: Tighten heuristics for BEGIN/END within CREATE SCHEMA.
> Branch: master Release: REL_19_BR [d51697484] 2026-04-06 15:16:25 -0400
> Support more object types within CREATE SCHEMA.
> Branch: master Release: REL_19_BR [404db8f9e] 2026-04-06 15:16:25 -0400
> Execute foreign key constraints in CREATE SCHEMA at the end.
> Branch: master Release: REL_19_BR [a9c350d9e] 2026-04-06 15:16:25 -0400
> Don't try to re-order the subcommands of CREATE SCHEMA.
>
> It looks like it might be possible to revert a9c350d9e and d51697484
> while keeping 404db8f9e, which would preserve a feature clearly
> required by the SQL standard (F311-01, "Support for circular
> references in that <referential constraint definition>s in two
> different <table definition>s may reference columns in the other
> table") while not breaking compatibility with our pre-19 behavior.
> Any opinions about whether that's a good thing to try to do, rather
> than a wholesale revert?
I don't have concerns specific to that change. In general, since the changes
were part of a composed series, I'd ascribe non-negligible risk to keeping
just part of the series. Maybe leave it in master but revert from v19? On
the other hand, the change is contained to a low-risk topic. I don't have a
strong preference between those choices.
> I'm also inclined to keep this bit of 049b742da:
>
> This patch also fixes what seems to me (tgl) a small bug: \;
> would reset BEGIN/END detection even when inside parens or BEGIN.
> That's unlike what a plain semicolon would do, and no such effect
> is suggested by the documentation.
What's a command that notices the difference? Something like a sql-standard
function body where the user backslashed the semicolons even though it wasn't
necessary?
I agree it's conceptually right to keep that bit. Resetting detection makes
sense at an unescaped semicolon that ends the query buffer, but that doesn't
extend to an escaped semicolon. I'm somewhat struggling to map out the
implications for existing scripts, but that's fair for a major release.
^ permalink raw reply [nested|flat] 55+ messages in thread
* Re: CREATE SCHEMA ... CREATE DOMAIN support
2024-11-12 12:54 CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-23 05:19 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-27 03:42 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-27 06:38 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-27 18:39 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-11-28 05:27 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-28 05:52 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-11-28 09:58 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-29 13:47 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-30 19:08 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-12-03 19:35 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Peter Eisentraut <peter@eisentraut.org>
2024-12-03 20:07 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-12-11 06:22 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-12-11 17:08 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2025-08-19 03:36 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2025-08-22 08:59 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2025-09-02 21:24 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2025-12-17 13:29 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2026-04-03 18:35 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2026-04-05 18:42 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2026-04-05 20:15 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2026-04-06 02:29 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2026-04-06 03:04 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2026-04-06 19:19 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2026-09-04 00:29 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Noah Misch <noah@leadboat.com>
2026-09-04 01:00 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2026-09-04 03:51 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Noah Misch <noah@leadboat.com>
2026-09-04 14:21 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2026-09-04 18:07 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Noah Misch <noah@leadboat.com>
2026-09-04 19:30 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2026-09-09 15:53 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Heikki Linnakangas <hlinnaka@iki.fi>
2026-09-09 16:17 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Nathan Bossart <nathandbossart@gmail.com>
2026-09-09 16:44 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2026-09-09 18:02 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Noah Misch <noah@leadboat.com>
@ 2026-09-09 18:28 ` Tom Lane <tgl@sss.pgh.pa.us>
2026-09-09 20:47 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
0 siblings, 1 reply; 55+ messages in thread
From: Tom Lane @ 2026-09-09 18:28 UTC (permalink / raw)
To: Noah Misch <noah@leadboat.com>; +Cc: Nathan Bossart <nathandbossart@gmail.com>; Heikki Linnakangas <hlinnaka@iki.fi>; jian he <jian.universality@gmail.com>; Kirill Reshke <reshkekirill@gmail.com>; Peter Eisentraut <peter@eisentraut.org>; PostgreSQL Hackers <pgsql-hackers@lists.postgresql.org>
Noah Misch <noah@leadboat.com> writes:
> On Wed, Sep 09, 2026 at 12:44:51PM -0400, Tom Lane wrote:
>> It looks like it might be possible to revert a9c350d9e and d51697484
>> while keeping 404db8f9e, which would preserve a feature clearly
>> required by the SQL standard (F311-01, "Support for circular
>> references in that <referential constraint definition>s in two
>> different <table definition>s may reference columns in the other
>> table") while not breaking compatibility with our pre-19 behavior.
>> Any opinions about whether that's a good thing to try to do, rather
>> than a wholesale revert?
> I don't have concerns specific to that change. In general, since the changes
> were part of a composed series, I'd ascribe non-negligible risk to keeping
> just part of the series. Maybe leave it in master but revert from v19? On
> the other hand, the change is contained to a low-risk topic. I don't have a
> strong preference between those choices.
I agree that the risk is not zero, but it would be nice to salvage
something from this work ;-). Also, I can't help noticing that we
have claimed full compatibility with F311-01 for years, and that's
been a lie up till these patches fixed it.
>> I'm also inclined to keep this bit of 049b742da:
>> This patch also fixes what seems to me (tgl) a small bug: \;
>> would reset BEGIN/END detection even when inside parens or BEGIN.
>> That's unlike what a plain semicolon would do, and no such effect
>> is suggested by the documentation.
> What's a command that notices the difference? Something like a sql-standard
> function body where the user backslashed the semicolons even though it wasn't
> necessary?
Exactly.
> I agree it's conceptually right to keep that bit. Resetting detection makes
> sense at an unescaped semicolon that ends the query buffer, but that doesn't
> extend to an escaped semicolon. I'm somewhat struggling to map out the
> implications for existing scripts, but that's fair for a major release.
It turns out that I had included that bit in the back-patched versions
of 3045a25ba (CVE-2026-6464), which maybe was overly bold of me but
I figured that that was the state of the code that we'd validated
while testing master. So it's a done deal now and undoing it in v19
wouldn't improve matters.
I've finished a patch that reverts 049b742da and d51697484 (attached),
and looking at it, I can't help noticing that those commits in
themselves don't create any backwards-compatibility issues: they
only add more subcommand types to what CREATE SCHEMA accepts. The
fundamental problem here is just that a9c350d9e changes existing
behavior. So perhaps another way forward could be to back off that
decision in some way that preserves the behavior in existing cases
while not re-ordering these additional subcommand types. I suppose
it's too late to start designing something like that for v19, but
maybe it'll be possible to un-revert the attached in v20.
regards, tom lane
Attachments:
[text/x-diff] v1-0001-Revert-Support-more-object-types-within-CREATE-SC.patch (32.0K, ../../704997.1788978513@sss.pgh.pa.us/2-v1-0001-Revert-Support-more-object-types-within-CREATE-SC.patch)
download | inline diff:
From 451259435be19b4ce85320e5a36d00dd980597ee Mon Sep 17 00:00:00 2001
From: Tom Lane <tgl@sss.pgh.pa.us>
Date: Wed, 9 Sep 2026 14:11:32 -0400
Subject: [PATCH v1] Revert "Support more object types within CREATE SCHEMA".
This reverts commit d516974840f4059d331ae6057ede3e4edd3c6747,
along with parts of commit 049b742daad0965be4a846035408ae27ce1f9e14
("psql: Tighten heuristics for BEGIN/END within CREATE SCHEMA").
While there's nothing particularly wrong with d51697484 in itself,
it depends on a9c350d9e ("Don't try to re-order the subcommands of
CREATE SCHEMA"), and concerns have been raised that the compatibility
impact of that may be too great.
It's not possible to revert 049b742da verbatim, because the
CVE-2026-6464 patches 3045a25ba ("Teach psql to skip in-line COPY
... FROM STDIN data after a failure") and cf754f741 ("Save/restore
more lexer state when skipping text due to \if") depend on
infrastructure it added. Instead, rip out just the bits specifically
needed to parse CREATE FUNCTION within CREATE SCHEMA. This results in
psql code that matches v18-and-earlier as modified by CVE-2026-6464.
---
doc/src/sgml/ref/create_schema.sgml | 25 +--
src/backend/parser/gram.y | 3 -
src/backend/parser/parse_utilcmd.c | 88 -----------
src/bin/psql/psqlscanslash.l | 8 -
src/bin/psql/tab-complete.in.c | 22 ++-
src/fe_utils/psqlscan.l | 61 ++------
src/include/fe_utils/psqlscan_int.h | 6 -
.../expected/create_schema.out | 38 +----
.../test_ddl_deparse/sql/create_schema.sql | 18 +--
src/test/regress/expected/create_schema.out | 147 +-----------------
src/test/regress/sql/create_schema.sql | 81 +---------
11 files changed, 31 insertions(+), 466 deletions(-)
diff --git a/doc/src/sgml/ref/create_schema.sgml b/doc/src/sgml/ref/create_schema.sgml
index 4ecf82d6bcb..96bc496e777 100644
--- a/doc/src/sgml/ref/create_schema.sgml
+++ b/doc/src/sgml/ref/create_schema.sgml
@@ -100,27 +100,12 @@ 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 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
+ 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
within <command>CREATE SCHEMA</command>. Other kinds of objects may
- be created within the schema in separate commands after the schema
- is created.
+ be created 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 1015d303fbf..143e2876388 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -1653,11 +1653,8 @@ 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 d83616a8507..c532a49b21d 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -124,8 +124,6 @@ static void transformConstraintAttrs(ParseState *pstate,
static void transformColumnType(CreateStmtContext *cxt, ColumnDef *column);
static void checkSchemaNameRV(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);
@@ -4205,68 +4203,6 @@ 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;
-
- checkSchemaNameRV(pstate, schemaName, elp->typevar);
- elements = lappend(elements, element);
- }
- break;
-
case T_GrantStmt:
elements = lappend(elements, element);
break;
@@ -4314,30 +4250,6 @@ checkSchemaNameRV(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/psqlscanslash.l b/src/bin/psql/psqlscanslash.l
index 298473afda8..fd1f042de52 100644
--- a/src/bin/psql/psqlscanslash.l
+++ b/src/bin/psql/psqlscanslash.l
@@ -738,8 +738,6 @@ psql_scan_get_lex_state(PsqlScanState state)
PsqlScanStateSave *lex_state = pg_malloc_object(PsqlScanStateSave);
StaticAssertDecl(sizeof(lex_state->init_idents) == sizeof(state->init_idents),
"init_idents array lengths must match");
- StaticAssertDecl(sizeof(lex_state->sub_idents) == sizeof(state->sub_idents),
- "sub_idents array lengths must match");
lex_state->paren_depth = state->paren_depth;
lex_state->begin_depth = state->begin_depth;
@@ -747,9 +745,6 @@ psql_scan_get_lex_state(PsqlScanState state)
lex_state->init_idents_count = state->init_idents_count;
memcpy(lex_state->init_idents, state->init_idents,
sizeof(lex_state->init_idents));
- lex_state->sub_idents_count = state->sub_idents_count;
- memcpy(lex_state->sub_idents, state->sub_idents,
- sizeof(lex_state->sub_idents));
return lex_state;
}
@@ -766,9 +761,6 @@ psql_scan_set_lex_state(PsqlScanState state,
state->init_idents_count = lex_state->init_idents_count;
memcpy(state->init_idents, lex_state->init_idents,
sizeof(state->init_idents));
- state->sub_idents_count = lex_state->sub_idents_count;
- memcpy(state->sub_idents, lex_state->sub_idents,
- sizeof(state->sub_idents));
}
/*
diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c
index 8e1a1d69740..8c292ce8898 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -2189,11 +2189,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("AGGREGATE", "COLLATION", "DOMAIN", "FUNCTION",
- "INDEX", "OPERATOR", "PROCEDURE", "SEQUENCE", "TABLE",
- "TEXT SEARCH CONFIGURATION", "TEXT SEARCH DICTIONARY",
- "TEXT SEARCH PARSER", "TEXT SEARCH TEMPLATE",
- "TRIGGER", "TYPE", "VIEW",
+ COMPLETE_WITH("TABLE", "VIEW", "INDEX", "SEQUENCE", "TRIGGER",
/* for INDEX and TABLE/SEQUENCE, respectively */
"UNIQUE", "UNLOGGED");
else
@@ -3493,15 +3489,15 @@ match_previous_words(int pattern_id,
else if (Matches("CREATE", "DATABASE", MatchAny, "STRATEGY"))
COMPLETE_WITH("WAL_LOG", "FILE_COPY");
- /* CREATE DOMAIN --- is allowed inside CREATE SCHEMA, so use TailMatches */
- else if (TailMatches("CREATE", "DOMAIN", MatchAny))
+ /* CREATE DOMAIN */
+ else if (Matches("CREATE", "DOMAIN", MatchAny))
COMPLETE_WITH("AS");
- else if (TailMatches("CREATE", "DOMAIN", MatchAny, "AS"))
+ else if (Matches("CREATE", "DOMAIN", MatchAny, "AS"))
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes);
- else if (TailMatches("CREATE", "DOMAIN", MatchAny, "AS", MatchAny))
+ else if (Matches("CREATE", "DOMAIN", MatchAny, "AS", MatchAny))
COMPLETE_WITH("COLLATE", "DEFAULT", "CONSTRAINT",
"NOT NULL", "NULL", "CHECK (");
- else if (TailMatches("CREATE", "DOMAIN", MatchAny, "COLLATE"))
+ else if (Matches("CREATE", "DOMAIN", MatchAny, "COLLATE"))
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_collations);
/* CREATE EXTENSION */
@@ -3846,10 +3842,10 @@ match_previous_words(int pattern_id,
else if (Matches("CREATE", "TABLESPACE", MatchAny, "OWNER", MatchAny))
COMPLETE_WITH("LOCATION");
-/* CREATE TEXT SEARCH --- is allowed inside CREATE SCHEMA, so use TailMatches */
- else if (TailMatches("CREATE", "TEXT", "SEARCH"))
+/* CREATE TEXT SEARCH */
+ else if (Matches("CREATE", "TEXT", "SEARCH"))
COMPLETE_WITH("CONFIGURATION", "DICTIONARY", "PARSER", "TEMPLATE");
- else if (TailMatches("CREATE", "TEXT", "SEARCH", "CONFIGURATION|DICTIONARY|PARSER|TEMPLATE", MatchAny))
+ else if (Matches("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 9cba4d8d691..76819112ba1 100644
--- a/src/fe_utils/psqlscan.l
+++ b/src/fe_utils/psqlscan.l
@@ -959,8 +959,8 @@ other .
/* LCOV_EXCL_STOP */
/*
- * Record the first few keywords/identifiers of a statement or CREATE
- * SCHEMA sub-statement in the idents[] array, of length idents_size.
+ * Record the first few keywords/identifiers of a statement
+ * in the idents[] array, of length idents_size.
* *idents_count is the number of entries filled so far.
*
* We record the interesting keywords using their first character, which
@@ -976,20 +976,18 @@ psqlscan_record_initial_keyword(const char *identifier,
if (*idents_count < idents_size)
{
/*
- * What we need to recognize is CREATE [OR REPLACE] FUNCTION/PROCEDURE
- * and CREATE SCHEMA. Checking for SCHEMA is useless but not harmful
- * in the CREATE SCHEMA sub-statement case. We record these keywords
- * in lower case. We also need to recognize COPY ... FROM STDIN.
- * (Note: the backend grammar doesn't distinguish STDIN from STDOUT,
- * so we should not do so here either.) We record these keywords in
- * upper case, to avoid conflicting with the first set.
+ * What we need to recognize is CREATE [OR REPLACE] FUNCTION/PROCEDURE.
+ * We record these keywords in lower case. We also need to recognize
+ * COPY ... FROM STDIN. (Note: the backend grammar doesn't
+ * distinguish STDIN from STDOUT, so we should not do so here either.)
+ * We record these keywords in upper case, to avoid conflicting with
+ * the first set.
*/
if (pg_strcasecmp(identifier, "create") == 0 ||
pg_strcasecmp(identifier, "function") == 0 ||
pg_strcasecmp(identifier, "procedure") == 0 ||
pg_strcasecmp(identifier, "or") == 0 ||
- pg_strcasecmp(identifier, "replace") == 0 ||
- pg_strcasecmp(identifier, "schema") == 0)
+ pg_strcasecmp(identifier, "replace") == 0)
idents[*idents_count] = pg_tolower((unsigned char) identifier[0]);
else if (pg_strcasecmp(identifier, "copy") == 0 ||
pg_strcasecmp(identifier, "from") == 0 ||
@@ -1052,10 +1050,8 @@ psqlscan_is_copy_from_stdin(PsqlScanState state)
* Short of writing a full parser here, the following heuristic should work.
*
* We track whether the beginning of the statement matches CREATE [OR REPLACE]
- * {FUNCTION|PROCEDURE}. For CREATE SCHEMA, track BEGIN .. END blocks only
- * after recognizing an embedded CREATE [OR REPLACE] {FUNCTION|PROCEDURE}
- * subcommand. Once one of these conditions holds, count BEGIN and END
- * pairs. We also have to account for CASE ... END.
+ * {FUNCTION|PROCEDURE}. If so, count BEGIN and END pairs. We also have to
+ * account for CASE ... END.
*
* 2. Record enough information for psqlscan_is_copy_from_stdin() to recognize
* COPY FROM STDIN commands.
@@ -1063,19 +1059,13 @@ psqlscan_is_copy_from_stdin(PsqlScanState state)
static void
psqlscan_track_identifier(PsqlScanState state, const char *identifier)
{
- bool is_create_schema;
-
/* None of this needs to happen when we're inside parentheses */
if (state->paren_depth != 0)
return;
/* Reset all my state at the start of each new statement */
if (state->init_idents_count == 0)
- {
memset(state->init_idents, 0, sizeof(state->init_idents));
- state->sub_idents_count = 0;
- memset(state->sub_idents, 0, sizeof(state->sub_idents));
- }
/* Record initial keywords if init_idents_count is small enough */
psqlscan_record_initial_keyword(identifier,
@@ -1084,34 +1074,9 @@ psqlscan_track_identifier(PsqlScanState state, const char *identifier)
&state->init_idents_count);
/*
- * In CREATE SCHEMA, track identifiers from each top-level CREATE schema
- * element separately, so that BEGIN/END tracking is enabled only within
- * CREATE [OR REPLACE] {FUNCTION|PROCEDURE} clauses.
- */
- is_create_schema = (state->init_idents[0] == 'c' &&
- state->init_idents[1] == 's');
- if (is_create_schema &&
- state->begin_depth == 0)
- {
- /* Reset sub-clause state at each top-level CREATE keyword */
- if (pg_strcasecmp(identifier, "create") == 0)
- {
- state->sub_idents_count = 0;
- memset(state->sub_idents, 0, sizeof(state->sub_idents));
- }
- /* ... and record the first few keywords following that */
- psqlscan_record_initial_keyword(identifier,
- state->sub_idents,
- lengthof(state->sub_idents),
- &state->sub_idents_count);
- }
-
- /*
- * Track BEGIN/CASE/END only when within an appropriate (sub) statement.
+ * Track BEGIN/CASE/END only when within an appropriate statement.
*/
- if (psqlscan_is_create_routine(state->init_idents) ||
- (is_create_schema &&
- psqlscan_is_create_routine(state->sub_idents)))
+ if (psqlscan_is_create_routine(state->init_idents))
{
if (pg_strcasecmp(identifier, "begin") == 0)
state->begin_depth++;
diff --git a/src/include/fe_utils/psqlscan_int.h b/src/include/fe_utils/psqlscan_int.h
index b1661afd4af..cdb883259d0 100644
--- a/src/include/fe_utils/psqlscan_int.h
+++ b/src/include/fe_utils/psqlscan_int.h
@@ -122,9 +122,6 @@ typedef struct PsqlScanStateData
int copy_stdin_count; /* number of COPY FROM STDIN commands */
int init_idents_count; /* # identifiers since start of statement */
char init_idents[8]; /* records the first few identifiers */
- int sub_idents_count; /* # identifiers since start of a CREATE
- * SCHEMA element */
- char sub_idents[4]; /* records the first few of those identifiers */
/*
* Callback functions provided by the program making use of the lexer,
@@ -150,9 +147,6 @@ typedef struct PsqlScanStateSave
int copy_stdin_count; /* number of COPY FROM STDIN commands */
int init_idents_count; /* # identifiers since start of statement */
char init_idents[8]; /* records the first few identifiers */
- int sub_idents_count; /* # identifiers since start of a CREATE
- * SCHEMA element */
- char sub_idents[4]; /* records the first few of those identifiers */
} PsqlScanStateSave;
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 a867786899b..6ed85ef7446 100644
--- a/src/test/modules/test_ddl_deparse/expected/create_schema.out
+++ b/src/test/modules/test_ddl_deparse/expected/create_schema.out
@@ -13,46 +13,10 @@ 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 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)
-;
+ CREATE VIEW bar AS SELECT * FROM foo;
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 7ba641d06d6..145aef2a75a 100644
--- a/src/test/modules/test_ddl_deparse/sql/create_schema.sql
+++ b/src/test/modules/test_ddl_deparse/sql/create_schema.sql
@@ -14,23 +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 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 VIEW bar AS SELECT * FROM foo;
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 b9ae4c402fd..b34b9988962 100644
--- a/src/test/regress/expected/create_schema.out
+++ b/src/test/regress/expected/create_schema.out
@@ -5,7 +5,7 @@
CREATE ROLE regress_create_schema_role SUPERUSER;
-- Cases where schema creation fails as objects are qualified with a schema
-- that does not match with what's expected.
--- This checks most object types that include schema qualifications.
+-- This checks all the object types that include schema qualifications.
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)
@@ -32,10 +32,6 @@ CREATE SCHEMA AUTHORIZATION regress_create_schema_role
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...
^
-CREATE SCHEMA AUTHORIZATION regress_create_schema_role
- CREATE FUNCTION schema_not_existing.func(int) RETURNS int
- AS 'SELECT $1' LANGUAGE sql;
-ERROR: CREATE specifies a schema (schema_not_existing) different from the one being created (regress_create_schema_role)
-- Again, with a role specification and no schema names.
SET ROLE regress_create_schema_role;
CREATE SCHEMA AUTHORIZATION CURRENT_ROLE
@@ -181,146 +177,5 @@ 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
- -- this checks that psql is not fooled by an irrelevant BEGIN
- CREATE VIEW begin AS SELECT 1 AS one
- 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
- )
- GRANT USAGE ON TYPE cs_type TO public
-;
-NOTICE: return type cs_type is only a shell
-NOTICE: argument type cs_type is only a shell
-LINE 33: 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 17 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 view regress_schema_misc.begin
-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 526bb3cb065..0f2accc59ec 100644
--- a/src/test/regress/sql/create_schema.sql
+++ b/src/test/regress/sql/create_schema.sql
@@ -8,7 +8,7 @@ CREATE ROLE regress_create_schema_role SUPERUSER;
-- Cases where schema creation fails as objects are qualified with a schema
-- that does not match with what's expected.
--- This checks most object types that include schema qualifications.
+-- This checks all the object types that include schema qualifications.
CREATE SCHEMA AUTHORIZATION regress_create_schema_role
CREATE SEQUENCE schema_not_existing.seq;
CREATE SCHEMA AUTHORIZATION regress_create_schema_role
@@ -20,9 +20,6 @@ CREATE SCHEMA AUTHORIZATION regress_create_schema_role
CREATE SCHEMA AUTHORIZATION regress_create_schema_role
CREATE TRIGGER schema_trig BEFORE INSERT ON schema_not_existing.tab
EXECUTE FUNCTION schema_trig.no_func();
-CREATE SCHEMA AUTHORIZATION regress_create_schema_role
- CREATE FUNCTION schema_not_existing.func(int) RETURNS int
- AS 'SELECT $1' LANGUAGE sql;
-- Again, with a role specification and no schema names.
SET ROLE regress_create_schema_role;
CREATE SCHEMA AUTHORIZATION CURRENT_ROLE
@@ -101,81 +98,5 @@ 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
-
- -- this checks that psql is not fooled by an irrelevant BEGIN
- CREATE VIEW begin AS SELECT 1 AS one
-
- 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
- )
-
- GRANT USAGE ON TYPE cs_type TO public
-;
-
-\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.52.0
^ permalink raw reply [nested|flat] 55+ messages in thread
* Re: CREATE SCHEMA ... CREATE DOMAIN support
2024-11-12 12:54 CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-23 05:19 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-27 03:42 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-27 06:38 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-27 18:39 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-11-28 05:27 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-28 05:52 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-11-28 09:58 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-29 13:47 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2024-11-30 19:08 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-12-03 19:35 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Peter Eisentraut <peter@eisentraut.org>
2024-12-03 20:07 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2024-12-11 06:22 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-12-11 17:08 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2025-08-19 03:36 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2025-08-22 08:59 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2025-09-02 21:24 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2025-12-17 13:29 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2026-04-03 18:35 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2026-04-05 18:42 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2026-04-05 20:15 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2026-04-06 02:29 ` Re: CREATE SCHEMA ... CREATE DOMAIN support jian he <jian.universality@gmail.com>
2026-04-06 03:04 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2026-04-06 19:19 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2026-09-04 00:29 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Noah Misch <noah@leadboat.com>
2026-09-04 01:00 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2026-09-04 03:51 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Noah Misch <noah@leadboat.com>
2026-09-04 14:21 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2026-09-04 18:07 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Noah Misch <noah@leadboat.com>
2026-09-04 19:30 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2026-09-09 15:53 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Heikki Linnakangas <hlinnaka@iki.fi>
2026-09-09 16:17 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Nathan Bossart <nathandbossart@gmail.com>
2026-09-09 16:44 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
2026-09-09 18:02 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Noah Misch <noah@leadboat.com>
2026-09-09 18:28 ` Re: CREATE SCHEMA ... CREATE DOMAIN support Tom Lane <tgl@sss.pgh.pa.us>
@ 2026-09-09 20:47 ` Tom Lane <tgl@sss.pgh.pa.us>
0 siblings, 0 replies; 55+ messages in thread
From: Tom Lane @ 2026-09-09 20:47 UTC (permalink / raw)
To: Noah Misch <noah@leadboat.com>; +Cc: Nathan Bossart <nathandbossart@gmail.com>; Heikki Linnakangas <hlinnaka@iki.fi>; jian he <jian.universality@gmail.com>; Kirill Reshke <reshkekirill@gmail.com>; Peter Eisentraut <peter@eisentraut.org>; PostgreSQL Hackers <pgsql-hackers@lists.postgresql.org>
Here's a finished reversion proposal that keeps 404db8f9e.
0001 is the same patch I posted earlier today.
(This is against master, but I've confirmed it works on v19 too.)
regards, tom lane
Attachments:
[text/x-diff] v1-0001-Revert-Support-more-object-types-within-CREATE-SC.patch (32.0K, ../../839739.1788986830@sss.pgh.pa.us/2-v1-0001-Revert-Support-more-object-types-within-CREATE-SC.patch)
download | inline diff:
From 451259435be19b4ce85320e5a36d00dd980597ee Mon Sep 17 00:00:00 2001
From: Tom Lane <tgl@sss.pgh.pa.us>
Date: Wed, 9 Sep 2026 14:11:32 -0400
Subject: [PATCH v1 1/2] Revert "Support more object types within CREATE
SCHEMA".
This reverts commit d516974840f4059d331ae6057ede3e4edd3c6747,
along with parts of commit 049b742daad0965be4a846035408ae27ce1f9e14
("psql: Tighten heuristics for BEGIN/END within CREATE SCHEMA").
While there's nothing particularly wrong with d51697484 in itself,
it depends on a9c350d9e ("Don't try to re-order the subcommands of
CREATE SCHEMA"), and concerns have been raised that the compatibility
impact of that may be too great.
It's not possible to revert 049b742da verbatim, because the
CVE-2026-6464 patches 3045a25ba ("Teach psql to skip in-line COPY
... FROM STDIN data after a failure") and cf754f741 ("Save/restore
more lexer state when skipping text due to \if") depend on
infrastructure it added. Instead, rip out just the bits specifically
needed to parse CREATE FUNCTION within CREATE SCHEMA. This results in
psql code that matches v18-and-earlier as modified by CVE-2026-6464.
---
doc/src/sgml/ref/create_schema.sgml | 25 +--
src/backend/parser/gram.y | 3 -
src/backend/parser/parse_utilcmd.c | 88 -----------
src/bin/psql/psqlscanslash.l | 8 -
src/bin/psql/tab-complete.in.c | 22 ++-
src/fe_utils/psqlscan.l | 61 ++------
src/include/fe_utils/psqlscan_int.h | 6 -
.../expected/create_schema.out | 38 +----
.../test_ddl_deparse/sql/create_schema.sql | 18 +--
src/test/regress/expected/create_schema.out | 147 +-----------------
src/test/regress/sql/create_schema.sql | 81 +---------
11 files changed, 31 insertions(+), 466 deletions(-)
diff --git a/doc/src/sgml/ref/create_schema.sgml b/doc/src/sgml/ref/create_schema.sgml
index 4ecf82d6bcb..96bc496e777 100644
--- a/doc/src/sgml/ref/create_schema.sgml
+++ b/doc/src/sgml/ref/create_schema.sgml
@@ -100,27 +100,12 @@ 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 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
+ 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
within <command>CREATE SCHEMA</command>. Other kinds of objects may
- be created within the schema in separate commands after the schema
- is created.
+ be created 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 1015d303fbf..143e2876388 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -1653,11 +1653,8 @@ 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 d83616a8507..c532a49b21d 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -124,8 +124,6 @@ static void transformConstraintAttrs(ParseState *pstate,
static void transformColumnType(CreateStmtContext *cxt, ColumnDef *column);
static void checkSchemaNameRV(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);
@@ -4205,68 +4203,6 @@ 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;
-
- checkSchemaNameRV(pstate, schemaName, elp->typevar);
- elements = lappend(elements, element);
- }
- break;
-
case T_GrantStmt:
elements = lappend(elements, element);
break;
@@ -4314,30 +4250,6 @@ checkSchemaNameRV(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/psqlscanslash.l b/src/bin/psql/psqlscanslash.l
index 298473afda8..fd1f042de52 100644
--- a/src/bin/psql/psqlscanslash.l
+++ b/src/bin/psql/psqlscanslash.l
@@ -738,8 +738,6 @@ psql_scan_get_lex_state(PsqlScanState state)
PsqlScanStateSave *lex_state = pg_malloc_object(PsqlScanStateSave);
StaticAssertDecl(sizeof(lex_state->init_idents) == sizeof(state->init_idents),
"init_idents array lengths must match");
- StaticAssertDecl(sizeof(lex_state->sub_idents) == sizeof(state->sub_idents),
- "sub_idents array lengths must match");
lex_state->paren_depth = state->paren_depth;
lex_state->begin_depth = state->begin_depth;
@@ -747,9 +745,6 @@ psql_scan_get_lex_state(PsqlScanState state)
lex_state->init_idents_count = state->init_idents_count;
memcpy(lex_state->init_idents, state->init_idents,
sizeof(lex_state->init_idents));
- lex_state->sub_idents_count = state->sub_idents_count;
- memcpy(lex_state->sub_idents, state->sub_idents,
- sizeof(lex_state->sub_idents));
return lex_state;
}
@@ -766,9 +761,6 @@ psql_scan_set_lex_state(PsqlScanState state,
state->init_idents_count = lex_state->init_idents_count;
memcpy(state->init_idents, lex_state->init_idents,
sizeof(state->init_idents));
- state->sub_idents_count = lex_state->sub_idents_count;
- memcpy(state->sub_idents, lex_state->sub_idents,
- sizeof(state->sub_idents));
}
/*
diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c
index 8e1a1d69740..8c292ce8898 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -2189,11 +2189,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("AGGREGATE", "COLLATION", "DOMAIN", "FUNCTION",
- "INDEX", "OPERATOR", "PROCEDURE", "SEQUENCE", "TABLE",
- "TEXT SEARCH CONFIGURATION", "TEXT SEARCH DICTIONARY",
- "TEXT SEARCH PARSER", "TEXT SEARCH TEMPLATE",
- "TRIGGER", "TYPE", "VIEW",
+ COMPLETE_WITH("TABLE", "VIEW", "INDEX", "SEQUENCE", "TRIGGER",
/* for INDEX and TABLE/SEQUENCE, respectively */
"UNIQUE", "UNLOGGED");
else
@@ -3493,15 +3489,15 @@ match_previous_words(int pattern_id,
else if (Matches("CREATE", "DATABASE", MatchAny, "STRATEGY"))
COMPLETE_WITH("WAL_LOG", "FILE_COPY");
- /* CREATE DOMAIN --- is allowed inside CREATE SCHEMA, so use TailMatches */
- else if (TailMatches("CREATE", "DOMAIN", MatchAny))
+ /* CREATE DOMAIN */
+ else if (Matches("CREATE", "DOMAIN", MatchAny))
COMPLETE_WITH("AS");
- else if (TailMatches("CREATE", "DOMAIN", MatchAny, "AS"))
+ else if (Matches("CREATE", "DOMAIN", MatchAny, "AS"))
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes);
- else if (TailMatches("CREATE", "DOMAIN", MatchAny, "AS", MatchAny))
+ else if (Matches("CREATE", "DOMAIN", MatchAny, "AS", MatchAny))
COMPLETE_WITH("COLLATE", "DEFAULT", "CONSTRAINT",
"NOT NULL", "NULL", "CHECK (");
- else if (TailMatches("CREATE", "DOMAIN", MatchAny, "COLLATE"))
+ else if (Matches("CREATE", "DOMAIN", MatchAny, "COLLATE"))
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_collations);
/* CREATE EXTENSION */
@@ -3846,10 +3842,10 @@ match_previous_words(int pattern_id,
else if (Matches("CREATE", "TABLESPACE", MatchAny, "OWNER", MatchAny))
COMPLETE_WITH("LOCATION");
-/* CREATE TEXT SEARCH --- is allowed inside CREATE SCHEMA, so use TailMatches */
- else if (TailMatches("CREATE", "TEXT", "SEARCH"))
+/* CREATE TEXT SEARCH */
+ else if (Matches("CREATE", "TEXT", "SEARCH"))
COMPLETE_WITH("CONFIGURATION", "DICTIONARY", "PARSER", "TEMPLATE");
- else if (TailMatches("CREATE", "TEXT", "SEARCH", "CONFIGURATION|DICTIONARY|PARSER|TEMPLATE", MatchAny))
+ else if (Matches("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 9cba4d8d691..76819112ba1 100644
--- a/src/fe_utils/psqlscan.l
+++ b/src/fe_utils/psqlscan.l
@@ -959,8 +959,8 @@ other .
/* LCOV_EXCL_STOP */
/*
- * Record the first few keywords/identifiers of a statement or CREATE
- * SCHEMA sub-statement in the idents[] array, of length idents_size.
+ * Record the first few keywords/identifiers of a statement
+ * in the idents[] array, of length idents_size.
* *idents_count is the number of entries filled so far.
*
* We record the interesting keywords using their first character, which
@@ -976,20 +976,18 @@ psqlscan_record_initial_keyword(const char *identifier,
if (*idents_count < idents_size)
{
/*
- * What we need to recognize is CREATE [OR REPLACE] FUNCTION/PROCEDURE
- * and CREATE SCHEMA. Checking for SCHEMA is useless but not harmful
- * in the CREATE SCHEMA sub-statement case. We record these keywords
- * in lower case. We also need to recognize COPY ... FROM STDIN.
- * (Note: the backend grammar doesn't distinguish STDIN from STDOUT,
- * so we should not do so here either.) We record these keywords in
- * upper case, to avoid conflicting with the first set.
+ * What we need to recognize is CREATE [OR REPLACE] FUNCTION/PROCEDURE.
+ * We record these keywords in lower case. We also need to recognize
+ * COPY ... FROM STDIN. (Note: the backend grammar doesn't
+ * distinguish STDIN from STDOUT, so we should not do so here either.)
+ * We record these keywords in upper case, to avoid conflicting with
+ * the first set.
*/
if (pg_strcasecmp(identifier, "create") == 0 ||
pg_strcasecmp(identifier, "function") == 0 ||
pg_strcasecmp(identifier, "procedure") == 0 ||
pg_strcasecmp(identifier, "or") == 0 ||
- pg_strcasecmp(identifier, "replace") == 0 ||
- pg_strcasecmp(identifier, "schema") == 0)
+ pg_strcasecmp(identifier, "replace") == 0)
idents[*idents_count] = pg_tolower((unsigned char) identifier[0]);
else if (pg_strcasecmp(identifier, "copy") == 0 ||
pg_strcasecmp(identifier, "from") == 0 ||
@@ -1052,10 +1050,8 @@ psqlscan_is_copy_from_stdin(PsqlScanState state)
* Short of writing a full parser here, the following heuristic should work.
*
* We track whether the beginning of the statement matches CREATE [OR REPLACE]
- * {FUNCTION|PROCEDURE}. For CREATE SCHEMA, track BEGIN .. END blocks only
- * after recognizing an embedded CREATE [OR REPLACE] {FUNCTION|PROCEDURE}
- * subcommand. Once one of these conditions holds, count BEGIN and END
- * pairs. We also have to account for CASE ... END.
+ * {FUNCTION|PROCEDURE}. If so, count BEGIN and END pairs. We also have to
+ * account for CASE ... END.
*
* 2. Record enough information for psqlscan_is_copy_from_stdin() to recognize
* COPY FROM STDIN commands.
@@ -1063,19 +1059,13 @@ psqlscan_is_copy_from_stdin(PsqlScanState state)
static void
psqlscan_track_identifier(PsqlScanState state, const char *identifier)
{
- bool is_create_schema;
-
/* None of this needs to happen when we're inside parentheses */
if (state->paren_depth != 0)
return;
/* Reset all my state at the start of each new statement */
if (state->init_idents_count == 0)
- {
memset(state->init_idents, 0, sizeof(state->init_idents));
- state->sub_idents_count = 0;
- memset(state->sub_idents, 0, sizeof(state->sub_idents));
- }
/* Record initial keywords if init_idents_count is small enough */
psqlscan_record_initial_keyword(identifier,
@@ -1084,34 +1074,9 @@ psqlscan_track_identifier(PsqlScanState state, const char *identifier)
&state->init_idents_count);
/*
- * In CREATE SCHEMA, track identifiers from each top-level CREATE schema
- * element separately, so that BEGIN/END tracking is enabled only within
- * CREATE [OR REPLACE] {FUNCTION|PROCEDURE} clauses.
- */
- is_create_schema = (state->init_idents[0] == 'c' &&
- state->init_idents[1] == 's');
- if (is_create_schema &&
- state->begin_depth == 0)
- {
- /* Reset sub-clause state at each top-level CREATE keyword */
- if (pg_strcasecmp(identifier, "create") == 0)
- {
- state->sub_idents_count = 0;
- memset(state->sub_idents, 0, sizeof(state->sub_idents));
- }
- /* ... and record the first few keywords following that */
- psqlscan_record_initial_keyword(identifier,
- state->sub_idents,
- lengthof(state->sub_idents),
- &state->sub_idents_count);
- }
-
- /*
- * Track BEGIN/CASE/END only when within an appropriate (sub) statement.
+ * Track BEGIN/CASE/END only when within an appropriate statement.
*/
- if (psqlscan_is_create_routine(state->init_idents) ||
- (is_create_schema &&
- psqlscan_is_create_routine(state->sub_idents)))
+ if (psqlscan_is_create_routine(state->init_idents))
{
if (pg_strcasecmp(identifier, "begin") == 0)
state->begin_depth++;
diff --git a/src/include/fe_utils/psqlscan_int.h b/src/include/fe_utils/psqlscan_int.h
index b1661afd4af..cdb883259d0 100644
--- a/src/include/fe_utils/psqlscan_int.h
+++ b/src/include/fe_utils/psqlscan_int.h
@@ -122,9 +122,6 @@ typedef struct PsqlScanStateData
int copy_stdin_count; /* number of COPY FROM STDIN commands */
int init_idents_count; /* # identifiers since start of statement */
char init_idents[8]; /* records the first few identifiers */
- int sub_idents_count; /* # identifiers since start of a CREATE
- * SCHEMA element */
- char sub_idents[4]; /* records the first few of those identifiers */
/*
* Callback functions provided by the program making use of the lexer,
@@ -150,9 +147,6 @@ typedef struct PsqlScanStateSave
int copy_stdin_count; /* number of COPY FROM STDIN commands */
int init_idents_count; /* # identifiers since start of statement */
char init_idents[8]; /* records the first few identifiers */
- int sub_idents_count; /* # identifiers since start of a CREATE
- * SCHEMA element */
- char sub_idents[4]; /* records the first few of those identifiers */
} PsqlScanStateSave;
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 a867786899b..6ed85ef7446 100644
--- a/src/test/modules/test_ddl_deparse/expected/create_schema.out
+++ b/src/test/modules/test_ddl_deparse/expected/create_schema.out
@@ -13,46 +13,10 @@ 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 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)
-;
+ CREATE VIEW bar AS SELECT * FROM foo;
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 7ba641d06d6..145aef2a75a 100644
--- a/src/test/modules/test_ddl_deparse/sql/create_schema.sql
+++ b/src/test/modules/test_ddl_deparse/sql/create_schema.sql
@@ -14,23 +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 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 VIEW bar AS SELECT * FROM foo;
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 b9ae4c402fd..b34b9988962 100644
--- a/src/test/regress/expected/create_schema.out
+++ b/src/test/regress/expected/create_schema.out
@@ -5,7 +5,7 @@
CREATE ROLE regress_create_schema_role SUPERUSER;
-- Cases where schema creation fails as objects are qualified with a schema
-- that does not match with what's expected.
--- This checks most object types that include schema qualifications.
+-- This checks all the object types that include schema qualifications.
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)
@@ -32,10 +32,6 @@ CREATE SCHEMA AUTHORIZATION regress_create_schema_role
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...
^
-CREATE SCHEMA AUTHORIZATION regress_create_schema_role
- CREATE FUNCTION schema_not_existing.func(int) RETURNS int
- AS 'SELECT $1' LANGUAGE sql;
-ERROR: CREATE specifies a schema (schema_not_existing) different from the one being created (regress_create_schema_role)
-- Again, with a role specification and no schema names.
SET ROLE regress_create_schema_role;
CREATE SCHEMA AUTHORIZATION CURRENT_ROLE
@@ -181,146 +177,5 @@ 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
- -- this checks that psql is not fooled by an irrelevant BEGIN
- CREATE VIEW begin AS SELECT 1 AS one
- 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
- )
- GRANT USAGE ON TYPE cs_type TO public
-;
-NOTICE: return type cs_type is only a shell
-NOTICE: argument type cs_type is only a shell
-LINE 33: 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 17 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 view regress_schema_misc.begin
-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 526bb3cb065..0f2accc59ec 100644
--- a/src/test/regress/sql/create_schema.sql
+++ b/src/test/regress/sql/create_schema.sql
@@ -8,7 +8,7 @@ CREATE ROLE regress_create_schema_role SUPERUSER;
-- Cases where schema creation fails as objects are qualified with a schema
-- that does not match with what's expected.
--- This checks most object types that include schema qualifications.
+-- This checks all the object types that include schema qualifications.
CREATE SCHEMA AUTHORIZATION regress_create_schema_role
CREATE SEQUENCE schema_not_existing.seq;
CREATE SCHEMA AUTHORIZATION regress_create_schema_role
@@ -20,9 +20,6 @@ CREATE SCHEMA AUTHORIZATION regress_create_schema_role
CREATE SCHEMA AUTHORIZATION regress_create_schema_role
CREATE TRIGGER schema_trig BEFORE INSERT ON schema_not_existing.tab
EXECUTE FUNCTION schema_trig.no_func();
-CREATE SCHEMA AUTHORIZATION regress_create_schema_role
- CREATE FUNCTION schema_not_existing.func(int) RETURNS int
- AS 'SELECT $1' LANGUAGE sql;
-- Again, with a role specification and no schema names.
SET ROLE regress_create_schema_role;
CREATE SCHEMA AUTHORIZATION CURRENT_ROLE
@@ -101,81 +98,5 @@ 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
-
- -- this checks that psql is not fooled by an irrelevant BEGIN
- CREATE VIEW begin AS SELECT 1 AS one
-
- 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
- )
-
- GRANT USAGE ON TYPE cs_type TO public
-;
-
-\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.52.0
[text/x-diff] v1-0002-Revert-Don-t-try-to-re-order-the-subcommands-of-C.patch (17.3K, ../../839739.1788986830@sss.pgh.pa.us/3-v1-0002-Revert-Don-t-try-to-re-order-the-subcommands-of-C.patch)
download | inline diff:
From 18806a814175b52ba55c25ea1266088c00612b40 Mon Sep 17 00:00:00 2001
From: Tom Lane <tgl@sss.pgh.pa.us>
Date: Wed, 9 Sep 2026 16:02:37 -0400
Subject: [PATCH v1 2/2] Revert "Don't try to re-order the subcommands of
CREATE SCHEMA".
This reverts commit a9c350d9ee66745aadcf7c0c95a567752a762171.
While that intentionally changed our semantics for CREATE SCHEMA
subcommands, it's being argued that the functionality gain does
not justify potentially-subtle compatibility breakage. The most
critical bit of functionality gain came from commit 404db8f9e
("Execute foreign key constraints in CREATE SCHEMA at the end"),
which we're keeping because it's required by SQL spec.
This is not an exact revert, partly because we're keeping 404db8f9e,
and partly because I kept the API changes that allowed passing down
a ParseState (which allows providing an error cursor for many of the
errors thrown in CREATE SCHEMA).
---
doc/src/sgml/ref/create_schema.sgml | 23 ++--
src/backend/commands/schemacmds.c | 7 +-
src/backend/parser/parse_utilcmd.c | 116 +++++++++++++-------
src/test/regress/expected/create_schema.out | 7 --
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 +
9 files changed, 102 insertions(+), 79 deletions(-)
diff --git a/doc/src/sgml/ref/create_schema.sgml b/doc/src/sgml/ref/create_schema.sgml
index 96bc496e777..31c381a2b7a 100644
--- a/doc/src/sgml/ref/create_schema.sgml
+++ b/doc/src/sgml/ref/create_schema.sgml
@@ -131,14 +131,6 @@ 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.
- 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>
<refsect1>
@@ -201,12 +193,15 @@ CREATE VIEW hollywood.winners AS
</para>
<para>
- 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.
+ The SQL standard suggests, without saying so in so many words, that the
+ subcommands in <command>CREATE SCHEMA</command> can appear in any
+ order; but the only actual requirement it gives is that foreign keys
+ referencing table definitions that appear in later <command>CREATE
+ SCHEMA</command> subcommands should be accepted. The
+ present <productname>PostgreSQL</productname> implementation handles
+ that case, but does not handle all other cases of forward references in
+ subcommands; it might sometimes be necessary to reorder the subcommands
+ in order to avoid forward references.
</para>
<para>
diff --git a/src/backend/commands/schemacmds.c b/src/backend/commands/schemacmds.c
index de5bbd5662c..b016983f904 100644
--- a/src/backend/commands/schemacmds.c
+++ b/src/backend/commands/schemacmds.c
@@ -189,9 +189,10 @@ CreateSchemaCommand(ParseState *pstate, CreateSchemaStmt *stmt,
/*
* Examine the list of commands embedded in the CREATE SCHEMA command, and
- * 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.
+ * 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.
*/
parsetree_list = transformCreateSchemaStmtElements(pstate,
stmt->schemaElts,
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index c532a49b21d..f838311090b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -96,6 +96,20 @@ typedef struct
bool ofType; /* true if statement contains OF typename */
} CreateStmtContext;
+/* State shared by transformCreateSchemaStmtElements and its subroutines */
+typedef struct
+{
+ ParseState *pstate; /* overall parse state */
+ 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 */
+ List *foreign_keys; /* generated ALTER ADD FOREIGN KEY items */
+} CreateSchemaStmtContext;
+
static void transformColumnDefinition(CreateStmtContext *cxt,
ColumnDef *column);
@@ -122,8 +136,7 @@ static void transformCheckConstraints(CreateStmtContext *cxt,
static void transformConstraintAttrs(ParseState *pstate,
List *constraintList);
static void transformColumnType(CreateStmtContext *cxt, ColumnDef *column);
-static void checkSchemaNameRV(ParseState *pstate, const char *context_schema,
- RangeVar *relation);
+static void checkSchemaNameRV(CreateSchemaStmtContext *cxt, RangeVar *relation);
static CreateStmt *transformCreateSchemaCreateTable(ParseState *pstate,
CreateStmt *stmt,
List **fk_elements);
@@ -4112,17 +4125,17 @@ transformColumnType(CreateStmtContext *cxt, ColumnDef *column)
* transformCreateSchemaStmtElements -
* analyzes the elements of a CREATE SCHEMA statement
*
- * 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, except for foreign keys.
+ * This presently has two responsibilities. We verify that no subcommands
+ * are trying to create objects outside the new schema. We also attempt to
+ * re-order the subcommands 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,
+ * and it's unlikely that we can do significantly better while working with
+ * non-parse-analyzed commands. The only case that the SQL standard calls
+ * out as required is to support forward references in foreign-key constraint
+ * clauses in CREATE TABLE subcommands. We do handle that, by pulling out
+ * such clauses and converting them to ALTER TABLE ADD CONSTRAINT commands
+ * appended to the list.
*
* "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
@@ -4140,17 +4153,28 @@ List *
transformCreateSchemaStmtElements(ParseState *pstate, List *schemaElts,
const char *schemaName)
{
- List *elements = NIL;
- List *fk_elements = NIL;
- ListCell *lc;
+ CreateSchemaStmtContext cxt;
+ List *result;
+ ListCell *elements;
+
+ cxt.pstate = pstate;
+ cxt.schemaname = schemaName;
+ cxt.sequences = NIL;
+ cxt.tables = NIL;
+ cxt.views = NIL;
+ cxt.indexes = NIL;
+ cxt.triggers = NIL;
+ cxt.grants = NIL;
+ cxt.foreign_keys = NIL;
/*
* Run through each schema element in the schema element list. Check
- * target schema names, and collect the list of actions to be done.
+ * target schema names, separate statements by type, and do preliminary
+ * analysis.
*/
- foreach(lc, schemaElts)
+ foreach(elements, schemaElts)
{
- Node *element = lfirst(lc);
+ Node *element = lfirst(elements);
switch (nodeTag(element))
{
@@ -4158,8 +4182,8 @@ transformCreateSchemaStmtElements(ParseState *pstate, List *schemaElts,
{
CreateSeqStmt *elp = (CreateSeqStmt *) element;
- checkSchemaNameRV(pstate, schemaName, elp->sequence);
- elements = lappend(elements, element);
+ checkSchemaNameRV(&cxt, elp->sequence);
+ cxt.sequences = lappend(cxt.sequences, element);
}
break;
@@ -4167,12 +4191,16 @@ transformCreateSchemaStmtElements(ParseState *pstate, List *schemaElts,
{
CreateStmt *elp = (CreateStmt *) element;
- checkSchemaNameRV(pstate, schemaName, elp->relation);
+ checkSchemaNameRV(&cxt, elp->relation);
/* Pull out any foreign key clauses, add to fk_elements */
elp = transformCreateSchemaCreateTable(pstate,
elp,
- &fk_elements);
- elements = lappend(elements, elp);
+ &cxt.foreign_keys);
+
+ /*
+ * XXX todo: deal with other constraints
+ */
+ cxt.tables = lappend(cxt.tables, elp);
}
break;
@@ -4180,8 +4208,12 @@ transformCreateSchemaStmtElements(ParseState *pstate, List *schemaElts,
{
ViewStmt *elp = (ViewStmt *) element;
- checkSchemaNameRV(pstate, schemaName, elp->view);
- elements = lappend(elements, element);
+ checkSchemaNameRV(&cxt, elp->view);
+
+ /*
+ * XXX todo: deal with references between views
+ */
+ cxt.views = lappend(cxt.views, element);
}
break;
@@ -4189,8 +4221,8 @@ transformCreateSchemaStmtElements(ParseState *pstate, List *schemaElts,
{
IndexStmt *elp = (IndexStmt *) element;
- checkSchemaNameRV(pstate, schemaName, elp->relation);
- elements = lappend(elements, element);
+ checkSchemaNameRV(&cxt, elp->relation);
+ cxt.indexes = lappend(cxt.indexes, element);
}
break;
@@ -4198,13 +4230,13 @@ transformCreateSchemaStmtElements(ParseState *pstate, List *schemaElts,
{
CreateTrigStmt *elp = (CreateTrigStmt *) element;
- checkSchemaNameRV(pstate, schemaName, elp->relation);
- elements = lappend(elements, element);
+ checkSchemaNameRV(&cxt, elp->relation);
+ cxt.triggers = lappend(cxt.triggers, element);
}
break;
case T_GrantStmt:
- elements = lappend(elements, element);
+ cxt.grants = lappend(cxt.grants, element);
break;
default:
@@ -4213,7 +4245,16 @@ transformCreateSchemaStmtElements(ParseState *pstate, List *schemaElts,
}
}
- return list_concat(elements, fk_elements);
+ 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);
+ result = list_concat(result, cxt.foreign_keys);
+
+ return result;
}
/*
@@ -4228,17 +4269,16 @@ transformCreateSchemaStmtElements(ParseState *pstate, List *schemaElts,
* that would likewise put the object into the wrong schema.
*/
static void
-checkSchemaNameRV(ParseState *pstate, const char *context_schema,
- RangeVar *relation)
+checkSchemaNameRV(CreateSchemaStmtContext *cxt, RangeVar *relation)
{
if (relation->schemaname != NULL &&
- strcmp(context_schema, relation->schemaname) != 0)
+ strcmp(cxt->schemaname, relation->schemaname) != 0)
ereport(ERROR,
(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)));
+ relation->schemaname, cxt->schemaname),
+ parser_errposition(cxt->pstate, relation->location)));
if (relation->relpersistence == RELPERSISTENCE_TEMP)
{
@@ -4246,7 +4286,7 @@ checkSchemaNameRV(ParseState *pstate, const char *context_schema,
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("cannot create temporary relation in non-temporary schema"),
- parser_errposition(pstate, relation->location)));
+ parser_errposition(cxt->pstate, relation->location)));
}
}
diff --git a/src/test/regress/expected/create_schema.out b/src/test/regress/expected/create_schema.out
index b34b9988962..7a9b71f97b3 100644
--- a/src/test/regress/expected/create_schema.out
+++ b/src/test/regress/expected/create_schema.out
@@ -88,13 +88,6 @@ ERROR: CREATE specifies a schema (schema_not_existing) different from the one b
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/event_trigger.out b/src/test/regress/expected/event_trigger.out
index 065f586310f..f57e8ffa7a5 100644
--- a/src/test/regress/expected/event_trigger.out
+++ b/src/test/regress/expected/event_trigger.out
@@ -425,12 +425,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=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
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;
diff --git a/src/test/regress/expected/namespace.out b/src/test/regress/expected/namespace.out
index 2e582e783c2..dbbda72d395 100644
--- a/src/test/regress/expected/namespace.out
+++ b/src/test/regress/expected/namespace.out
@@ -10,14 +10,13 @@ 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 0f2accc59ec..57014ac4930 100644
--- a/src/test/regress/sql/create_schema.sql
+++ b/src/test/regress/sql/create_schema.sql
@@ -47,11 +47,6 @@ 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 a75d4f580d3..306cdc2d8c6 100644
--- a/src/test/regress/sql/namespace.sql
+++ b/src/test/regress/sql/namespace.sql
@@ -7,16 +7,15 @@
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 1040a65bc14..0e48edf4109 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -587,6 +587,7 @@ CreateRangeStmt
CreateReplicationSlotCmd
CreateRoleStmt
CreateSchemaStmt
+CreateSchemaStmtContext
CreateSeqStmt
CreateStatsStmt
CreateStmt
--
2.52.0
^ permalink raw reply [nested|flat] 55+ messages in thread
end of thread, other threads:[~2026-09-09 20:47 UTC | newest]
Thread overview: 55+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2024-11-12 12:54 CREATE SCHEMA ... CREATE DOMAIN support Kirill Reshke <reshkekirill@gmail.com>
2024-11-23 05:19 ` jian he <jian.universality@gmail.com>
2024-11-27 03:42 ` jian he <jian.universality@gmail.com>
2024-11-27 03:47 ` Tom Lane <tgl@sss.pgh.pa.us>
2024-11-27 06:38 ` Kirill Reshke <reshkekirill@gmail.com>
2024-11-27 18:39 ` Tom Lane <tgl@sss.pgh.pa.us>
2024-11-28 05:27 ` Kirill Reshke <reshkekirill@gmail.com>
2024-11-28 05:52 ` Tom Lane <tgl@sss.pgh.pa.us>
2024-11-28 09:58 ` Kirill Reshke <reshkekirill@gmail.com>
2024-11-29 13:47 ` jian he <jian.universality@gmail.com>
2024-11-29 14:00 ` Kirill Reshke <reshkekirill@gmail.com>
2024-11-30 19:08 ` Tom Lane <tgl@sss.pgh.pa.us>
2024-11-30 23:33 ` Tom Lane <tgl@sss.pgh.pa.us>
2024-12-01 05:39 ` Kirill Reshke <reshkekirill@gmail.com>
2024-12-01 05:53 ` Tom Lane <tgl@sss.pgh.pa.us>
2024-12-01 09:59 ` jian he <jian.universality@gmail.com>
2024-12-01 16:57 ` Tom Lane <tgl@sss.pgh.pa.us>
2024-12-01 22:30 ` Tom Lane <tgl@sss.pgh.pa.us>
2024-12-02 01:34 ` Michael Paquier <michael@paquier.xyz>
2024-12-02 02:15 ` Tom Lane <tgl@sss.pgh.pa.us>
2024-12-02 16:08 ` Vik Fearing <vik@postgresfriends.org>
2024-12-02 16:56 ` Tom Lane <tgl@sss.pgh.pa.us>
2024-12-02 17:26 ` Vik Fearing <vik@postgresfriends.org>
2024-12-03 19:35 ` Peter Eisentraut <peter@eisentraut.org>
2024-12-03 20:07 ` Tom Lane <tgl@sss.pgh.pa.us>
2024-12-11 06:22 ` Kirill Reshke <reshkekirill@gmail.com>
2024-12-11 17:08 ` Tom Lane <tgl@sss.pgh.pa.us>
2025-08-19 03:36 ` jian he <jian.universality@gmail.com>
2025-08-22 08:59 ` Kirill Reshke <reshkekirill@gmail.com>
2025-08-26 04:53 ` jian he <jian.universality@gmail.com>
2025-09-02 21:24 ` Tom Lane <tgl@sss.pgh.pa.us>
2025-12-17 13:29 ` jian he <jian.universality@gmail.com>
2025-12-31 01:40 ` jian he <jian.universality@gmail.com>
2026-01-19 15:06 ` jian he <jian.universality@gmail.com>
2026-03-10 14:11 ` jian he <jian.universality@gmail.com>
2026-03-30 02:44 ` jian he <jian.universality@gmail.com>
2026-04-03 18:35 ` Tom Lane <tgl@sss.pgh.pa.us>
2026-04-05 18:42 ` Tom Lane <tgl@sss.pgh.pa.us>
2026-04-05 20:15 ` Tom Lane <tgl@sss.pgh.pa.us>
2026-04-05 22:05 ` Tom Lane <tgl@sss.pgh.pa.us>
2026-04-06 02:29 ` jian he <jian.universality@gmail.com>
2026-04-06 03:04 ` Tom Lane <tgl@sss.pgh.pa.us>
2026-04-06 19:19 ` Tom Lane <tgl@sss.pgh.pa.us>
2026-09-04 00:29 ` Noah Misch <noah@leadboat.com>
2026-09-04 01:00 ` Tom Lane <tgl@sss.pgh.pa.us>
2026-09-04 03:51 ` Noah Misch <noah@leadboat.com>
2026-09-04 14:21 ` Tom Lane <tgl@sss.pgh.pa.us>
2026-09-04 18:07 ` Noah Misch <noah@leadboat.com>
2026-09-04 19:30 ` Tom Lane <tgl@sss.pgh.pa.us>
2026-09-09 15:53 ` Heikki Linnakangas <hlinnaka@iki.fi>
2026-09-09 16:17 ` Nathan Bossart <nathandbossart@gmail.com>
2026-09-09 16:44 ` Tom Lane <tgl@sss.pgh.pa.us>
2026-09-09 18:02 ` Noah Misch <noah@leadboat.com>
2026-09-09 18:28 ` Tom Lane <tgl@sss.pgh.pa.us>
2026-09-09 20:47 ` Tom Lane <tgl@sss.pgh.pa.us>
This inbox is served by DDX for PostgreSQL; see mirroring instructions
for how to clone and mirror all data and code used for this inbox