Skip to content

Commit c690ebb

Browse files
committed
Further fixes for CREATE TABLE LIKE: cope with self-referential FKs.
Commit 5028981 was too careless about the order of execution of the additional ALTER TABLE operations generated by expandTableLikeClause. It just stuck them all at the end, which seems okay for most purposes. But it falls down in the case where LIKE is importing a primary key or unique index and the outer CREATE TABLE includes a FOREIGN KEY constraint that needs to depend on that index. Weird as that is, it used to work, so we ought to keep it working. To fix, make parse_utilcmd.c insert LIKE clauses between index-creation and FK-creation commands in the transformed list of commands, and change utility.c so that the commands generated by expandTableLikeClause are executed immediately not at the end. One could imagine scenarios where this wouldn't work either; but currently expandTableLikeClause only makes column default expressions, CHECK constraints, and indexes, and this ordering seems fine for those. Per bug #16730 from Sofoklis Papasofokli. Like the previous patch, back-patch to all supported branches. Discussion: https://postgr.es/m/16730-b902f7e6e0276b30@postgresql.org
1 parent 6b8235d commit c690ebb

File tree

4 files changed

+56
-20
lines changed

4 files changed

+56
-20
lines changed

src/backend/parser/parse_utilcmd.c

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ typedef struct
8585
List *ckconstraints; /* CHECK constraints */
8686
List *fkconstraints; /* FOREIGN KEY constraints */
8787
List *ixconstraints; /* index-creating constraints */
88+
List *likeclauses; /* LIKE clauses that need post-processing */
8889
List *extstats; /* cloned extended statistics */
8990
List *blist; /* "before list" of things to do before
9091
* creating the table */
@@ -240,6 +241,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
240241
cxt.ckconstraints = NIL;
241242
cxt.fkconstraints = NIL;
242243
cxt.ixconstraints = NIL;
244+
cxt.likeclauses = NIL;
243245
cxt.extstats = NIL;
244246
cxt.blist = NIL;
245247
cxt.alist = NIL;
@@ -331,6 +333,20 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
331333
*/
332334
transformIndexConstraints(&cxt);
333335

336+
/*
337+
* Re-consideration of LIKE clauses should happen after creation of
338+
* indexes, but before creation of foreign keys. This order is critical
339+
* because a LIKE clause may attempt to create a primary key. If there's
340+
* also a pkey in the main CREATE TABLE list, creation of that will not
341+
* check for a duplicate at runtime (since index_check_primary_key()
342+
* expects that we rejected dups here). Creation of the LIKE-generated
343+
* pkey behaves like ALTER TABLE ADD, so it will check, but obviously that
344+
* only works if it happens second. On the other hand, we want to make
345+
* pkeys before foreign key constraints, in case the user tries to make a
346+
* self-referential FK.
347+
*/
348+
cxt.alist = list_concat(cxt.alist, cxt.likeclauses);
349+
334350
/*
335351
* Postprocess foreign-key constraints.
336352
*/
@@ -891,7 +907,7 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
891907
* Change the LIKE <srctable> portion of a CREATE TABLE statement into
892908
* column definitions that recreate the user defined column portions of
893909
* <srctable>. Also, if there are any LIKE options that we can't fully
894-
* process at this point, add the TableLikeClause to cxt->alist, which
910+
* process at this point, add the TableLikeClause to cxt->likeclauses, which
895911
* will cause utility.c to call expandTableLikeClause() after the new
896912
* table has been created.
897913
*/
@@ -1083,13 +1099,13 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
10831099
* We cannot yet deal with CHECK constraints or indexes, since we don't
10841100
* yet know what column numbers the copied columns will have in the
10851101
* finished table. If any of those options are specified, add the LIKE
1086-
* clause to cxt->alist so that expandTableLikeClause will be called after
1087-
* we do know that.
1102+
* clause to cxt->likeclauses so that expandTableLikeClause will be called
1103+
* after we do know that.
10881104
*/
10891105
if (table_like_clause->options &
10901106
(CREATE_TABLE_LIKE_CONSTRAINTS |
10911107
CREATE_TABLE_LIKE_INDEXES))
1092-
cxt->alist = lappend(cxt->alist, table_like_clause);
1108+
cxt->likeclauses = lappend(cxt->likeclauses, table_like_clause);
10931109

10941110
/*
10951111
* We may copy extended statistics if requested, since the representation
@@ -2534,7 +2550,7 @@ transformFKConstraints(CreateStmtContext *cxt,
25342550
* Note: the ADD CONSTRAINT command must also execute after any index
25352551
* creation commands. Thus, this should run after
25362552
* transformIndexConstraints, so that the CREATE INDEX commands are
2537-
* already in cxt->alist.
2553+
* already in cxt->alist. See also the handling of cxt->likeclauses.
25382554
*/
25392555
if (!isAddConstraint)
25402556
{
@@ -3037,6 +3053,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
30373053
cxt.ckconstraints = NIL;
30383054
cxt.fkconstraints = NIL;
30393055
cxt.ixconstraints = NIL;
3056+
cxt.likeclauses = NIL;
30403057
cxt.extstats = NIL;
30413058
cxt.blist = NIL;
30423059
cxt.alist = NIL;

src/backend/tcop/utility.c

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -990,17 +990,22 @@ ProcessUtilitySlow(ParseState *pstate,
990990
case T_CreateForeignTableStmt:
991991
{
992992
List *stmts;
993-
ListCell *l;
994993
RangeVar *table_rv = NULL;
995994

996995
/* Run parse analysis ... */
997996
stmts = transformCreateStmt((CreateStmt *) parsetree,
998997
queryString);
999998

1000-
/* ... and do it */
1001-
foreach(l, stmts)
999+
/*
1000+
* ... and do it. We can't use foreach() because we may
1001+
* modify the list midway through, so pick off the
1002+
* elements one at a time, the hard way.
1003+
*/
1004+
while (stmts != NIL)
10021005
{
1003-
Node *stmt = (Node *) lfirst(l);
1006+
Node *stmt = (Node *) linitial(stmts);
1007+
1008+
stmts = list_delete_first(stmts);
10041009

10051010
if (IsA(stmt, CreateStmt))
10061011
{
@@ -1066,23 +1071,16 @@ ProcessUtilitySlow(ParseState *pstate,
10661071
/*
10671072
* Do delayed processing of LIKE options. This
10681073
* will result in additional sub-statements for us
1069-
* to process. We can just tack those onto the
1070-
* to-do list.
1074+
* to process. Those should get done before any
1075+
* remaining actions, so prepend them to "stmts".
10711076
*/
10721077
TableLikeClause *like = (TableLikeClause *) stmt;
10731078
List *morestmts;
10741079

10751080
Assert(table_rv != NULL);
10761081

10771082
morestmts = expandTableLikeClause(table_rv, like);
1078-
stmts = list_concat(stmts, morestmts);
1079-
1080-
/*
1081-
* We don't need a CCI now, besides which the "l"
1082-
* list pointer is now possibly invalid, so just
1083-
* skip the CCI test below.
1084-
*/
1085-
continue;
1083+
stmts = list_concat(morestmts, stmts);
10861084
}
10871085
else
10881086
{
@@ -1110,7 +1108,7 @@ ProcessUtilitySlow(ParseState *pstate,
11101108
}
11111109

11121110
/* Need CCI between commands */
1113-
if (lnext(l) != NULL)
1111+
if (stmts != NIL)
11141112
CommandCounterIncrement();
11151113
}
11161114

src/test/regress/expected/create_table_like.out

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,22 @@ INSERT INTO inhg (xx, yy, x) VALUES ('foo', 10, 15); -- should fail
161161
ERROR: duplicate key value violates unique constraint "inhg_x_key"
162162
DETAIL: Key (x)=(15) already exists.
163163
DROP TABLE inhg;
164+
DROP TABLE inhz;
165+
/* Use primary key imported by LIKE for self-referential FK constraint */
166+
CREATE TABLE inhz (x text REFERENCES inhz, LIKE inhx INCLUDING INDEXES);
167+
\d inhz
168+
Table "public.inhz"
169+
Column | Type | Collation | Nullable | Default
170+
--------+------+-----------+----------+---------
171+
x | text | | |
172+
xx | text | | not null |
173+
Indexes:
174+
"inhz_pkey" PRIMARY KEY, btree (xx)
175+
Foreign-key constraints:
176+
"inhz_x_fkey" FOREIGN KEY (x) REFERENCES inhz(xx)
177+
Referenced by:
178+
TABLE "inhz" CONSTRAINT "inhz_x_fkey" FOREIGN KEY (x) REFERENCES inhz(xx)
179+
164180
DROP TABLE inhz;
165181
-- including storage and comments
166182
CREATE TABLE ctlt1 (a text CHECK (length(a) > 2) PRIMARY KEY, b text);

src/test/regress/sql/create_table_like.sql

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,11 @@ INSERT INTO inhg (xx, yy, x) VALUES ('foo', 10, 15); -- should fail
8080
DROP TABLE inhg;
8181
DROP TABLE inhz;
8282

83+
/* Use primary key imported by LIKE for self-referential FK constraint */
84+
CREATE TABLE inhz (x text REFERENCES inhz, LIKE inhx INCLUDING INDEXES);
85+
\d inhz
86+
DROP TABLE inhz;
87+
8388
-- including storage and comments
8489
CREATE TABLE ctlt1 (a text CHECK (length(a) > 2) PRIMARY KEY, b text);
8590
CREATE INDEX ctlt1_b_key ON ctlt1 (b);

0 commit comments

Comments
 (0)