1
0
mirror of https://github.com/postgres/postgres.git synced 2026-10-23 13:29:09 +03:00

Revert "Don't try to re-order the subcommands of CREATE SCHEMA".

This reverts commit a9c350d9ee.
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
useful 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 and doesn't
seem to create a compatibility hazard by itself.

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

Author: Tom Lane <tgl@sss.pgh.pa.us>
Discussion: https://postgr.es/m/20260904180712.cc.noahmisch@microsoft.com
Backpatch-through: 19
This commit is contained in:
Tom Lane
2026-09-11 11:38:16 -04:00
parent f1920437cd
commit 962e0a723a
9 changed files with 102 additions and 79 deletions

View File

@@ -131,14 +131,6 @@ CREATE SCHEMA IF NOT EXISTS AUTHORIZATION <replaceable class="parameter">role_sp
<literal>CREATE</literal> privilege for the current database. <literal>CREATE</literal> privilege for the current database.
(Of course, superusers bypass this check.) (Of course, superusers bypass this check.)
</para> </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>
<refsect1> <refsect1>
@@ -201,12 +193,15 @@ CREATE VIEW hollywood.winners AS
</para> </para>
<para> <para>
Some other SQL implementations attempt to allow more kinds of forward The SQL standard suggests, without saying so in so many words, that the
references to objects defined in subcommands in <command>CREATE SCHEMA</command> can appear in any
later <replaceable class="parameter">schema_element</replaceable> order; but the only actual requirement it gives is that foreign keys
subcommands than just foreign key constraints. This is difficult or referencing table definitions that appear in later <command>CREATE
impossible to do correctly in general, and it is not clear that the SQL SCHEMA</command> subcommands should be accepted. The
standard requires any such behavior except for foreign keys. 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>
<para> <para>

View File

@@ -189,9 +189,10 @@ CreateSchemaCommand(ParseState *pstate, CreateSchemaStmt *stmt,
/* /*
* Examine the list of commands embedded in the CREATE SCHEMA command, and * Examine the list of commands embedded in the CREATE SCHEMA command, and
* do preliminary transformations. Note that the result is still a list * reorganize them into a sequentially executable order with no forward
* of raw parsetrees --- we cannot, in general, run parse analysis on one * references. Note that the result is still a list of raw parsetrees ---
* statement until we have actually executed the prior ones. * we cannot, in general, run parse analysis on one statement until we
* have actually executed the prior ones.
*/ */
parsetree_list = transformCreateSchemaStmtElements(pstate, parsetree_list = transformCreateSchemaStmtElements(pstate,
stmt->schemaElts, stmt->schemaElts,

View File

@@ -96,6 +96,20 @@ typedef struct
bool ofType; /* true if statement contains OF typename */ bool ofType; /* true if statement contains OF typename */
} CreateStmtContext; } 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, static void transformColumnDefinition(CreateStmtContext *cxt,
ColumnDef *column); ColumnDef *column);
@@ -122,8 +136,7 @@ static void transformCheckConstraints(CreateStmtContext *cxt,
static void transformConstraintAttrs(ParseState *pstate, static void transformConstraintAttrs(ParseState *pstate,
List *constraintList); List *constraintList);
static void transformColumnType(CreateStmtContext *cxt, ColumnDef *column); static void transformColumnType(CreateStmtContext *cxt, ColumnDef *column);
static void checkSchemaNameRV(ParseState *pstate, const char *context_schema, static void checkSchemaNameRV(CreateSchemaStmtContext *cxt, RangeVar *relation);
RangeVar *relation);
static CreateStmt *transformCreateSchemaCreateTable(ParseState *pstate, static CreateStmt *transformCreateSchemaCreateTable(ParseState *pstate,
CreateStmt *stmt, CreateStmt *stmt,
List **fk_elements); List **fk_elements);
@@ -4112,17 +4125,17 @@ transformColumnType(CreateStmtContext *cxt, ColumnDef *column)
* transformCreateSchemaStmtElements - * transformCreateSchemaStmtElements -
* analyzes the elements of a CREATE SCHEMA statement * analyzes the elements of a CREATE SCHEMA statement
* *
* This presently has two responsibilities. We verify that no subcommands are * This presently has two responsibilities. We verify that no subcommands
* trying to create objects outside the new schema. We also pull out any * are trying to create objects outside the new schema. We also attempt to
* foreign-key constraint clauses embedded in CREATE TABLE subcommands, and * re-order the subcommands such that there are no forward references
* convert them to ALTER TABLE ADD CONSTRAINT commands appended to the list. * (e.g. GRANT to a table created later in the list). Note that the logic
* This supports forward references in foreign keys, which is required by the * we use for determining forward references is presently quite incomplete,
* SQL standard. * 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
* We used to try to re-order the commands in a way that would work even if * out as required is to support forward references in foreign-key constraint
* the user-written order would not, but that's too hard (perhaps impossible) * clauses in CREATE TABLE subcommands. We do handle that, by pulling out
* to do correctly with not-yet-parse-analyzed commands. Now we'll just * such clauses and converting them to ALTER TABLE ADD CONSTRAINT commands
* execute the elements in the order given, except for foreign keys. * appended to the list.
* *
* "schemaName" is the name of the schema that will be used for the creation * "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 * of the objects listed. It may be obtained from the schema name defined
@@ -4140,17 +4153,28 @@ List *
transformCreateSchemaStmtElements(ParseState *pstate, List *schemaElts, transformCreateSchemaStmtElements(ParseState *pstate, List *schemaElts,
const char *schemaName) const char *schemaName)
{ {
List *elements = NIL; CreateSchemaStmtContext cxt;
List *fk_elements = NIL; List *result;
ListCell *lc; 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 * 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)) switch (nodeTag(element))
{ {
@@ -4158,8 +4182,8 @@ transformCreateSchemaStmtElements(ParseState *pstate, List *schemaElts,
{ {
CreateSeqStmt *elp = (CreateSeqStmt *) element; CreateSeqStmt *elp = (CreateSeqStmt *) element;
checkSchemaNameRV(pstate, schemaName, elp->sequence); checkSchemaNameRV(&cxt, elp->sequence);
elements = lappend(elements, element); cxt.sequences = lappend(cxt.sequences, element);
} }
break; break;
@@ -4167,12 +4191,16 @@ transformCreateSchemaStmtElements(ParseState *pstate, List *schemaElts,
{ {
CreateStmt *elp = (CreateStmt *) element; CreateStmt *elp = (CreateStmt *) element;
checkSchemaNameRV(pstate, schemaName, elp->relation); checkSchemaNameRV(&cxt, elp->relation);
/* Pull out any foreign key clauses, add to fk_elements */ /* Pull out any foreign key clauses, add to fk_elements */
elp = transformCreateSchemaCreateTable(pstate, elp = transformCreateSchemaCreateTable(pstate,
elp, elp,
&fk_elements); &cxt.foreign_keys);
elements = lappend(elements, elp);
/*
* XXX todo: deal with other constraints
*/
cxt.tables = lappend(cxt.tables, elp);
} }
break; break;
@@ -4180,8 +4208,12 @@ transformCreateSchemaStmtElements(ParseState *pstate, List *schemaElts,
{ {
ViewStmt *elp = (ViewStmt *) element; ViewStmt *elp = (ViewStmt *) element;
checkSchemaNameRV(pstate, schemaName, elp->view); checkSchemaNameRV(&cxt, elp->view);
elements = lappend(elements, element);
/*
* XXX todo: deal with references between views
*/
cxt.views = lappend(cxt.views, element);
} }
break; break;
@@ -4189,8 +4221,8 @@ transformCreateSchemaStmtElements(ParseState *pstate, List *schemaElts,
{ {
IndexStmt *elp = (IndexStmt *) element; IndexStmt *elp = (IndexStmt *) element;
checkSchemaNameRV(pstate, schemaName, elp->relation); checkSchemaNameRV(&cxt, elp->relation);
elements = lappend(elements, element); cxt.indexes = lappend(cxt.indexes, element);
} }
break; break;
@@ -4198,13 +4230,13 @@ transformCreateSchemaStmtElements(ParseState *pstate, List *schemaElts,
{ {
CreateTrigStmt *elp = (CreateTrigStmt *) element; CreateTrigStmt *elp = (CreateTrigStmt *) element;
checkSchemaNameRV(pstate, schemaName, elp->relation); checkSchemaNameRV(&cxt, elp->relation);
elements = lappend(elements, element); cxt.triggers = lappend(cxt.triggers, element);
} }
break; break;
case T_GrantStmt: case T_GrantStmt:
elements = lappend(elements, element); cxt.grants = lappend(cxt.grants, element);
break; break;
default: 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. * that would likewise put the object into the wrong schema.
*/ */
static void static void
checkSchemaNameRV(ParseState *pstate, const char *context_schema, checkSchemaNameRV(CreateSchemaStmtContext *cxt, RangeVar *relation)
RangeVar *relation)
{ {
if (relation->schemaname != NULL && if (relation->schemaname != NULL &&
strcmp(context_schema, relation->schemaname) != 0) strcmp(cxt->schemaname, relation->schemaname) != 0)
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_INVALID_SCHEMA_DEFINITION), (errcode(ERRCODE_INVALID_SCHEMA_DEFINITION),
errmsg("CREATE specifies a schema (%s) " errmsg("CREATE specifies a schema (%s) "
"different from the one being created (%s)", "different from the one being created (%s)",
relation->schemaname, context_schema), relation->schemaname, cxt->schemaname),
parser_errposition(pstate, relation->location))); parser_errposition(cxt->pstate, relation->location)));
if (relation->relpersistence == RELPERSISTENCE_TEMP) if (relation->relpersistence == RELPERSISTENCE_TEMP)
{ {
@@ -4246,7 +4286,7 @@ checkSchemaNameRV(ParseState *pstate, const char *context_schema,
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION), (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("cannot create temporary relation in non-temporary schema"), errmsg("cannot create temporary relation in non-temporary schema"),
parser_errposition(pstate, relation->location))); parser_errposition(cxt->pstate, relation->location)));
} }
} }

View File

@@ -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... LINE 2: CREATE TRIGGER schema_trig BEFORE INSERT ON schema_not_exi...
^ ^
RESET ROLE; 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. -- Cases where the schema creation succeeds.
-- The schema created matches the role name. -- The schema created matches the role name.
CREATE SCHEMA AUTHORIZATION regress_create_schema_role CREATE SCHEMA AUTHORIZATION regress_create_schema_role

View File

@@ -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=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_a_seq
NOTICE: END: command_tag=ALTER SEQUENCE type=sequence identity=evttrig.one_col_c_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 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 SEQUENCE type=sequence identity=evttrig.id_col_d_seq
NOTICE: END: command_tag=CREATE TABLE type=table identity=evttrig.id 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 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 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 NOTICE: END: command_tag=ALTER TABLE type=table identity=evttrig.two
-- View with column additions -- View with column additions
CREATE OR REPLACE VIEW evttrig.one_view AS SELECT * FROM evttrig.two, evttrig.id; CREATE OR REPLACE VIEW evttrig.one_view AS SELECT * FROM evttrig.two, evttrig.id;

View File

@@ -10,14 +10,13 @@ SELECT pg_catalog.set_config('search_path', ' ', false);
(1 row) (1 row)
CREATE SCHEMA test_ns_schema_1 CREATE SCHEMA test_ns_schema_1
CREATE TABLE abc (
a serial,
b int UNIQUE
)
CREATE UNIQUE INDEX abc_a_idx ON abc (a) CREATE UNIQUE INDEX abc_a_idx ON abc (a)
CREATE VIEW abc_view AS CREATE VIEW abc_view AS
SELECT a+1 AS a, b+1 AS b FROM abc 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 -- verify that the correct search_path restored on abort
SET search_path to public; SET search_path to public;
BEGIN; BEGIN;

View File

@@ -47,11 +47,6 @@ CREATE SCHEMA regress_schema_1 AUTHORIZATION CURRENT_ROLE
EXECUTE FUNCTION schema_trig.no_func(); EXECUTE FUNCTION schema_trig.no_func();
RESET ROLE; 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. -- Cases where the schema creation succeeds.
-- The schema created matches the role name. -- The schema created matches the role name.
CREATE SCHEMA AUTHORIZATION regress_create_schema_role CREATE SCHEMA AUTHORIZATION regress_create_schema_role

View File

@@ -7,16 +7,15 @@
SELECT pg_catalog.set_config('search_path', ' ', false); SELECT pg_catalog.set_config('search_path', ' ', false);
CREATE SCHEMA test_ns_schema_1 CREATE SCHEMA test_ns_schema_1
CREATE TABLE abc (
a serial,
b int UNIQUE
)
CREATE UNIQUE INDEX abc_a_idx ON abc (a) CREATE UNIQUE INDEX abc_a_idx ON abc (a)
CREATE VIEW abc_view AS CREATE VIEW abc_view AS
SELECT a+1 AS a, b+1 AS b FROM abc 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 -- verify that the correct search_path restored on abort
SET search_path to public; SET search_path to public;

View File

@@ -587,6 +587,7 @@ CreateRangeStmt
CreateReplicationSlotCmd CreateReplicationSlotCmd
CreateRoleStmt CreateRoleStmt
CreateSchemaStmt CreateSchemaStmt
CreateSchemaStmtContext
CreateSeqStmt CreateSeqStmt
CreateStatsStmt CreateStatsStmt
CreateStmt CreateStmt