diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index 254c0f65c2..c709abad2b 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -86,6 +86,7 @@ typedef struct List *ckconstraints; /* CHECK constraints */ List *fkconstraints; /* FOREIGN KEY constraints */ List *ixconstraints; /* index-creating constraints */ + List *likeclauses; /* LIKE clauses that need post-processing */ List *extstats; /* cloned extended statistics */ List *blist; /* "before list" of things to do before * creating the table */ @@ -243,6 +244,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString) cxt.ckconstraints = NIL; cxt.fkconstraints = NIL; cxt.ixconstraints = NIL; + cxt.likeclauses = NIL; cxt.extstats = NIL; cxt.blist = NIL; cxt.alist = NIL; @@ -309,6 +311,20 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString) */ transformIndexConstraints(&cxt); + /* + * Re-consideration of LIKE clauses should happen after creation of + * indexes, but before creation of foreign keys. This order is critical + * because a LIKE clause may attempt to create a primary key. If there's + * also a pkey in the main CREATE TABLE list, creation of that will not + * check for a duplicate at runtime (since index_check_primary_key() + * expects that we rejected dups here). Creation of the LIKE-generated + * pkey behaves like ALTER TABLE ADD, so it will check, but obviously that + * only works if it happens second. On the other hand, we want to make + * pkeys before foreign key constraints, in case the user tries to make a + * self-referential FK. + */ + cxt.alist = list_concat(cxt.alist, cxt.likeclauses); + /* * Postprocess foreign-key constraints. */ @@ -923,7 +939,7 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint) * Change the LIKE portion of a CREATE TABLE statement into * column definitions that recreate the user defined column portions of * . Also, if there are any LIKE options that we can't fully - * process at this point, add the TableLikeClause to cxt->alist, which + * process at this point, add the TableLikeClause to cxt->likeclauses, which * will cause utility.c to call expandTableLikeClause() after the new * table has been created. */ @@ -1088,15 +1104,15 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla * We cannot yet deal with defaults, CHECK constraints, or indexes, since * we don't yet know what column numbers the copied columns will have in * the finished table. If any of those options are specified, add the - * LIKE clause to cxt->alist so that expandTableLikeClause will be called - * after we do know that. + * LIKE clause to cxt->likeclauses so that expandTableLikeClause will be + * called after we do know that. */ if (table_like_clause->options & (CREATE_TABLE_LIKE_DEFAULTS | CREATE_TABLE_LIKE_GENERATED | CREATE_TABLE_LIKE_CONSTRAINTS | CREATE_TABLE_LIKE_INDEXES)) - cxt->alist = lappend(cxt->alist, table_like_clause); + cxt->likeclauses = lappend(cxt->likeclauses, table_like_clause); /* * We may copy extended statistics if requested, since the representation @@ -2692,7 +2708,7 @@ transformFKConstraints(CreateStmtContext *cxt, * Note: the ADD CONSTRAINT command must also execute after any index * creation commands. Thus, this should run after * transformIndexConstraints, so that the CREATE INDEX commands are - * already in cxt->alist. + * already in cxt->alist. See also the handling of cxt->likeclauses. */ if (!isAddConstraint) { @@ -3205,6 +3221,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, cxt.ckconstraints = NIL; cxt.fkconstraints = NIL; cxt.ixconstraints = NIL; + cxt.likeclauses = NIL; cxt.extstats = NIL; cxt.blist = NIL; cxt.alist = NIL; diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index f398027fa6..81ac9b1cb2 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1138,17 +1138,22 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateForeignTableStmt: { List *stmts; - ListCell *l; RangeVar *table_rv = NULL; /* Run parse analysis ... */ stmts = transformCreateStmt((CreateStmt *) parsetree, queryString); - /* ... and do it */ - foreach(l, stmts) + /* + * ... and do it. We can't use foreach() because we may + * modify the list midway through, so pick off the + * elements one at a time, the hard way. + */ + while (stmts != NIL) { - Node *stmt = (Node *) lfirst(l); + Node *stmt = (Node *) linitial(stmts); + + stmts = list_delete_first(stmts); if (IsA(stmt, CreateStmt)) { @@ -1214,8 +1219,8 @@ ProcessUtilitySlow(ParseState *pstate, /* * Do delayed processing of LIKE options. This * will result in additional sub-statements for us - * to process. We can just tack those onto the - * to-do list. + * to process. Those should get done before any + * remaining actions, so prepend them to "stmts". */ TableLikeClause *like = (TableLikeClause *) stmt; List *morestmts; @@ -1223,14 +1228,7 @@ ProcessUtilitySlow(ParseState *pstate, Assert(table_rv != NULL); morestmts = expandTableLikeClause(table_rv, like); - stmts = list_concat(stmts, morestmts); - - /* - * We don't need a CCI now, besides which the "l" - * list pointer is now possibly invalid, so just - * skip the CCI test below. - */ - continue; + stmts = list_concat(morestmts, stmts); } else { @@ -1258,7 +1256,7 @@ ProcessUtilitySlow(ParseState *pstate, } /* Need CCI between commands */ - if (lnext(stmts, l) != NULL) + if (stmts != NIL) CommandCounterIncrement(); } diff --git a/src/test/regress/expected/create_table_like.out b/src/test/regress/expected/create_table_like.out index 912c73d351..01e3a8435d 100644 --- a/src/test/regress/expected/create_table_like.out +++ b/src/test/regress/expected/create_table_like.out @@ -282,6 +282,22 @@ INSERT INTO inhg (xx, yy, x) VALUES ('foo', 10, 15); -- should fail ERROR: duplicate key value violates unique constraint "inhg_x_key" DETAIL: Key (x)=(15) already exists. DROP TABLE inhg; +DROP TABLE inhz; +/* Use primary key imported by LIKE for self-referential FK constraint */ +CREATE TABLE inhz (x text REFERENCES inhz, LIKE inhx INCLUDING INDEXES); +\d inhz + Table "public.inhz" + Column | Type | Collation | Nullable | Default +--------+------+-----------+----------+--------- + x | text | | | + xx | text | | not null | +Indexes: + "inhz_pkey" PRIMARY KEY, btree (xx) +Foreign-key constraints: + "inhz_x_fkey" FOREIGN KEY (x) REFERENCES inhz(xx) +Referenced by: + TABLE "inhz" CONSTRAINT "inhz_x_fkey" FOREIGN KEY (x) REFERENCES inhz(xx) + DROP TABLE inhz; -- including storage and comments CREATE TABLE ctlt1 (a text CHECK (length(a) > 2) PRIMARY KEY, b text); diff --git a/src/test/regress/sql/create_table_like.sql b/src/test/regress/sql/create_table_like.sql index e484bac0a4..63548b3d11 100644 --- a/src/test/regress/sql/create_table_like.sql +++ b/src/test/regress/sql/create_table_like.sql @@ -114,6 +114,11 @@ INSERT INTO inhg (xx, yy, x) VALUES ('foo', 10, 15); -- should fail DROP TABLE inhg; DROP TABLE inhz; +/* Use primary key imported by LIKE for self-referential FK constraint */ +CREATE TABLE inhz (x text REFERENCES inhz, LIKE inhx INCLUDING INDEXES); +\d inhz +DROP TABLE inhz; + -- including storage and comments CREATE TABLE ctlt1 (a text CHECK (length(a) > 2) PRIMARY KEY, b text); CREATE INDEX ctlt1_b_key ON ctlt1 (b);