diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index 5c8a64d52e..51180c435a 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -85,6 +85,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 */ @@ -240,6 +241,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; @@ -331,6 +333,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. */ @@ -891,7 +907,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. */ @@ -1083,13 +1099,13 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla * We cannot yet deal with 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. + * clause to cxt->likeclauses so that expandTableLikeClause will be called + * after we do know that. */ if (table_like_clause->options & (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 @@ -2534,7 +2550,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) { @@ -3037,6 +3053,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 501fef96fa..cf117b5113 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -990,17 +990,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)) { @@ -1066,8 +1071,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; @@ -1075,14 +1080,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 { @@ -1110,7 +1108,7 @@ ProcessUtilitySlow(ParseState *pstate, } /* Need CCI between commands */ - if (lnext(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 fb2b2a03cd..7e43c50eb4 100644 --- a/src/test/regress/expected/create_table_like.out +++ b/src/test/regress/expected/create_table_like.out @@ -161,6 +161,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 79658b1ce6..8595d5e92d 100644 --- a/src/test/regress/sql/create_table_like.sql +++ b/src/test/regress/sql/create_table_like.sql @@ -80,6 +80,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);