From 42c983e59f77197d99a584f7f7d63446fffa0752 Mon Sep 17 00:00:00 2001 From: jian he 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 Author: Jian He Reviewed-by: Alvaro Herrera Reviewed-by: Tom Lane 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 role_sp An SQL statement defining an object to be created within the - schema. Currently, only CREATE - TABLE, CREATE VIEW, CREATE + schema. Currently, only CREATE DOMAIN + CREATE TABLE, CREATE VIEW, CREATE INDEX, CREATE SEQUENCE, CREATE TRIGGER and GRANT are accepted as clauses within CREATE SCHEMA. 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