Skip to content

Commit 6631d5f

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 d02dbba commit 6631d5f

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
@@ -78,6 +78,7 @@ typedef struct
7878
List *ckconstraints; /* CHECK constraints */
7979
List *fkconstraints; /* FOREIGN KEY constraints */
8080
List *ixconstraints; /* index-creating constraints */
81+
List *likeclauses; /* LIKE clauses that need post-processing */
8182
List *blist; /* "before list" of things to do before
8283
* creating the table */
8384
List *alist; /* "after list" of things to do after creating
@@ -221,6 +222,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
221222
cxt.ckconstraints = NIL;
222223
cxt.fkconstraints = NIL;
223224
cxt.ixconstraints = NIL;
225+
cxt.likeclauses = NIL;
224226
cxt.blist = NIL;
225227
cxt.alist = NIL;
226228
cxt.pkey = NULL;
@@ -285,6 +287,20 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
285287
*/
286288
transformIndexConstraints(&cxt);
287289

290+
/*
291+
* Re-consideration of LIKE clauses should happen after creation of
292+
* indexes, but before creation of foreign keys. This order is critical
293+
* because a LIKE clause may attempt to create a primary key. If there's
294+
* also a pkey in the main CREATE TABLE list, creation of that will not
295+
* check for a duplicate at runtime (since index_check_primary_key()
296+
* expects that we rejected dups here). Creation of the LIKE-generated
297+
* pkey behaves like ALTER TABLE ADD, so it will check, but obviously that
298+
* only works if it happens second. On the other hand, we want to make
299+
* pkeys before foreign key constraints, in case the user tries to make a
300+
* self-referential FK.
301+
*/
302+
cxt.alist = list_concat(cxt.alist, cxt.likeclauses);
303+
288304
/*
289305
* Postprocess foreign-key constraints.
290306
*/
@@ -695,7 +711,7 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
695711
* Change the LIKE <srctable> portion of a CREATE TABLE statement into
696712
* column definitions that recreate the user defined column portions of
697713
* <srctable>. Also, if there are any LIKE options that we can't fully
698-
* process at this point, add the TableLikeClause to cxt->alist, which
714+
* process at this point, add the TableLikeClause to cxt->likeclauses, which
699715
* will cause utility.c to call expandTableLikeClause() after the new
700716
* table has been created.
701717
*/
@@ -862,13 +878,13 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
862878
* We cannot yet deal with CHECK constraints or indexes, since we don't
863879
* yet know what column numbers the copied columns will have in the
864880
* finished table. If any of those options are specified, add the LIKE
865-
* clause to cxt->alist so that expandTableLikeClause will be called after
866-
* we do know that.
881+
* clause to cxt->likeclauses so that expandTableLikeClause will be called
882+
* after we do know that.
867883
*/
868884
if (table_like_clause->options &
869885
(CREATE_TABLE_LIKE_CONSTRAINTS |
870886
CREATE_TABLE_LIKE_INDEXES))
871-
cxt->alist = lappend(cxt->alist, table_like_clause);
887+
cxt->likeclauses = lappend(cxt->likeclauses, table_like_clause);
872888

873889
/*
874890
* Close the parent rel, but keep our AccessShareLock on it until xact
@@ -1991,7 +2007,7 @@ transformFKConstraints(CreateStmtContext *cxt,
19912007
* Note: the ADD CONSTRAINT command must also execute after any index
19922008
* creation commands. Thus, this should run after
19932009
* transformIndexConstraints, so that the CREATE INDEX commands are
1994-
* already in cxt->alist.
2010+
* already in cxt->alist. See also the handling of cxt->likeclauses.
19952011
*/
19962012
if (!isAddConstraint)
19972013
{
@@ -2500,6 +2516,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
25002516
cxt.ckconstraints = NIL;
25012517
cxt.fkconstraints = NIL;
25022518
cxt.ixconstraints = NIL;
2519+
cxt.likeclauses = NIL;
25032520
cxt.blist = NIL;
25042521
cxt.alist = NIL;
25052522
cxt.pkey = NULL;

src/backend/tcop/utility.c

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -948,17 +948,22 @@ ProcessUtilitySlow(Node *parsetree,
948948
case T_CreateForeignTableStmt:
949949
{
950950
List *stmts;
951-
ListCell *l;
952951
RangeVar *table_rv = NULL;
953952

954953
/* Run parse analysis ... */
955954
stmts = transformCreateStmt((CreateStmt *) parsetree,
956955
queryString);
957956

958-
/* ... and do it */
959-
foreach(l, stmts)
957+
/*
958+
* ... and do it. We can't use foreach() because we may
959+
* modify the list midway through, so pick off the
960+
* elements one at a time, the hard way.
961+
*/
962+
while (stmts != NIL)
960963
{
961-
Node *stmt = (Node *) lfirst(l);
964+
Node *stmt = (Node *) linitial(stmts);
965+
966+
stmts = list_delete_first(stmts);
962967

963968
if (IsA(stmt, CreateStmt))
964969
{
@@ -1022,23 +1027,16 @@ ProcessUtilitySlow(Node *parsetree,
10221027
/*
10231028
* Do delayed processing of LIKE options. This
10241029
* will result in additional sub-statements for us
1025-
* to process. We can just tack those onto the
1026-
* to-do list.
1030+
* to process. Those should get done before any
1031+
* remaining actions, so prepend them to "stmts".
10271032
*/
10281033
TableLikeClause *like = (TableLikeClause *) stmt;
10291034
List *morestmts;
10301035

10311036
Assert(table_rv != NULL);
10321037

10331038
morestmts = expandTableLikeClause(table_rv, like);
1034-
stmts = list_concat(stmts, morestmts);
1035-
1036-
/*
1037-
* We don't need a CCI now, besides which the "l"
1038-
* list pointer is now possibly invalid, so just
1039-
* skip the CCI test below.
1040-
*/
1041-
continue;
1039+
stmts = list_concat(morestmts, stmts);
10421040
}
10431041
else
10441042
{
@@ -1056,7 +1054,7 @@ ProcessUtilitySlow(Node *parsetree,
10561054
}
10571055

10581056
/* Need CCI between commands */
1059-
if (lnext(l) != NULL)
1057+
if (stmts != NIL)
10601058
CommandCounterIncrement();
10611059
}
10621060

src/test/regress/expected/create_table_like.out

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,22 @@ INSERT INTO inhg (xx, yy, x) VALUES ('foo', 10, 15); -- should fail
114114
ERROR: duplicate key value violates unique constraint "inhg_x_key"
115115
DETAIL: Key (x)=(15) already exists.
116116
DROP TABLE inhg;
117+
DROP TABLE inhz;
118+
/* Use primary key imported by LIKE for self-referential FK constraint */
119+
CREATE TABLE inhz (x text REFERENCES inhz, LIKE inhx INCLUDING INDEXES);
120+
\d inhz
121+
Table "public.inhz"
122+
Column | Type | Modifiers
123+
--------+------+-----------
124+
x | text |
125+
xx | text | not null
126+
Indexes:
127+
"inhz_pkey" PRIMARY KEY, btree (xx)
128+
Foreign-key constraints:
129+
"inhz_x_fkey" FOREIGN KEY (x) REFERENCES inhz(xx)
130+
Referenced by:
131+
TABLE "inhz" CONSTRAINT "inhz_x_fkey" FOREIGN KEY (x) REFERENCES inhz(xx)
132+
117133
DROP TABLE inhz;
118134
-- including storage and comments
119135
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
@@ -66,6 +66,11 @@ INSERT INTO inhg (xx, yy, x) VALUES ('foo', 10, 15); -- should fail
6666
DROP TABLE inhg;
6767
DROP TABLE inhz;
6868

69+
/* Use primary key imported by LIKE for self-referential FK constraint */
70+
CREATE TABLE inhz (x text REFERENCES inhz, LIKE inhx INCLUDING INDEXES);
71+
\d inhz
72+
DROP TABLE inhz;
73+
6974
-- including storage and comments
7075
CREATE TABLE ctlt1 (a text CHECK (length(a) > 2) PRIMARY KEY, b text);
7176
CREATE INDEX ctlt1_b_key ON ctlt1 (b);

0 commit comments

Comments
 (0)