From 8a504a948412ba11ba154d98676318983a00cfcc Mon Sep 17 00:00:00 2001 From: jian he 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