diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index a1c73fdc782..57ea3fa1163 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -81,6 +81,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 *blist; /* "before list" of things to do before * creating the table */ List *alist; /* "after list" of things to do after creating @@ -228,6 +229,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString) cxt.ckconstraints = NIL; cxt.fkconstraints = NIL; cxt.ixconstraints = NIL; + cxt.likeclauses = NIL; cxt.blist = NIL; cxt.alist = NIL; cxt.pkey = NULL; @@ -307,6 +309,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. */ @@ -722,7 +738,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. */ @@ -892,13 +908,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); /* * Close the parent rel, but keep our AccessShareLock on it until xact @@ -2065,7 +2081,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) { @@ -2574,6 +2590,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, cxt.ckconstraints = NIL; cxt.fkconstraints = NIL; cxt.ixconstraints = NIL; + cxt.likeclauses = NIL; cxt.blist = NIL; cxt.alist = NIL; cxt.pkey = NULL; diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 042feb96280..7c8eee07e80 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -963,17 +963,22 @@ ProcessUtilitySlow(Node *parsetree, 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)) { @@ -1037,8 +1042,8 @@ ProcessUtilitySlow(Node *parsetree, /* * 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; @@ -1046,14 +1051,7 @@ ProcessUtilitySlow(Node *parsetree, 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 { @@ -1071,7 +1069,7 @@ ProcessUtilitySlow(Node *parsetree, } /* 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 49e1c8066d6..4739cd5ee3c 100644 --- a/src/test/regress/expected/create_table_like.out +++ b/src/test/regress/expected/create_table_like.out @@ -114,6 +114,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 | Modifiers +--------+------+----------- + 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 92b03c3d4e4..86fa43415ed 100644 --- a/src/test/regress/sql/create_table_like.sql +++ b/src/test/regress/sql/create_table_like.sql @@ -66,6 +66,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);