Skip to content

Commit 3737965

Browse files
committed
Fix handling of CREATE TABLE LIKE with inheritance.
If a CREATE TABLE command uses both LIKE and traditional inheritance, Vars in CHECK constraints and expression indexes that are absorbed from a LIKE parent table tended to get mis-numbered, resulting in wrong answers and/or bizarre error messages (though probably not any actual crashes, thanks to validation occurring in the executor). In v12 and up, the same could happen to Vars in GENERATED expressions, even in cases with no LIKE clause but multiple traditional-inheritance parents. The cause of the problem for LIKE is that parse_utilcmd.c supposed it could renumber such Vars correctly during transformCreateStmt(), which it cannot since we have not yet accounted for columns added via inheritance. Fix that by postponing processing of LIKE INCLUDING CONSTRAINTS, DEFAULTS, GENERATED, INDEXES till after we've performed DefineRelation(). The error with GENERATED and multiple inheritance is a simple oversight in MergeAttributes(); it knows it has to renumber Vars in inherited CHECK constraints, but forgot to apply the same processing to inherited GENERATED expressions (a/k/a defaults). Per bug #16272 from Tom Gottfried. The non-GENERATED variants of the issue are ancient, presumably dating right back to the addition of CREATE TABLE LIKE; hence back-patch to all supported branches. Discussion: https://postgr.es/m/16272-6e32da020e9a9381@postgresql.org
1 parent 6910faa commit 3737965

File tree

5 files changed

+246
-88
lines changed

5 files changed

+246
-88
lines changed

src/backend/parser/parse_utilcmd.c

Lines changed: 157 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,6 @@ typedef struct
8585
List *ckconstraints; /* CHECK constraints */
8686
List *fkconstraints; /* FOREIGN KEY constraints */
8787
List *ixconstraints; /* index-creating constraints */
88-
List *inh_indexes; /* cloned indexes from INCLUDING INDEXES */
8988
List *extstats; /* cloned extended statistics */
9089
List *blist; /* "before list" of things to do before
9190
* creating the table */
@@ -150,6 +149,9 @@ static Const *transformPartitionBoundValue(ParseState *pstate, A_Const *con,
150149
* Returns a List of utility commands to be done in sequence. One of these
151150
* will be the transformed CreateStmt, but there may be additional actions
152151
* to be done before and after the actual DefineRelation() call.
152+
* In addition to normal utility commands such as AlterTableStmt and
153+
* IndexStmt, the result list may contain TableLikeClause(s), representing
154+
* the need to perform additional parse analysis after DefineRelation().
153155
*
154156
* SQL allows constraints to be scattered all over, so thumb through
155157
* the columns and collect all constraints into one place.
@@ -238,7 +240,6 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
238240
cxt.ckconstraints = NIL;
239241
cxt.fkconstraints = NIL;
240242
cxt.ixconstraints = NIL;
241-
cxt.inh_indexes = NIL;
242243
cxt.extstats = NIL;
243244
cxt.blist = NIL;
244245
cxt.alist = NIL;
@@ -888,8 +889,11 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
888889
* transformTableLikeClause
889890
*
890891
* Change the LIKE <srctable> portion of a CREATE TABLE statement into
891-
* column definitions which recreate the user defined column portions of
892-
* <srctable>.
892+
* column definitions that recreate the user defined column portions of
893+
* <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
895+
* will cause utility.c to call expandTableLikeClause() after the new
896+
* table has been created.
893897
*/
894898
static void
895899
transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_clause)
@@ -898,7 +902,6 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
898902
Relation relation;
899903
TupleDesc tupleDesc;
900904
TupleConstr *constr;
901-
AttrNumber *attmap;
902905
AclResult aclresult;
903906
char *comment;
904907
ParseCallbackState pcbstate;
@@ -912,6 +915,7 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
912915
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
913916
errmsg("LIKE is not supported for creating foreign tables")));
914917

918+
/* Open the relation referenced by the LIKE clause */
915919
relation = relation_openrv(table_like_clause->relation, AccessShareLock);
916920

917921
if (relation->rd_rel->relkind != RELKIND_RELATION &&
@@ -950,15 +954,10 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
950954
tupleDesc = RelationGetDescr(relation);
951955
constr = tupleDesc->constr;
952956

953-
/*
954-
* Initialize column number map for map_variable_attnos(). We need this
955-
* since dropped columns in the source table aren't copied, so the new
956-
* table can have different column numbers.
957-
*/
958-
attmap = (AttrNumber *) palloc0(sizeof(AttrNumber) * tupleDesc->natts);
959-
960957
/*
961958
* Insert the copied attributes into the cxt for the new table definition.
959+
* We must do this now so that they appear in the table in the relative
960+
* position where the LIKE clause is, as required by SQL99.
962961
*/
963962
for (parent_attno = 1; parent_attno <= tupleDesc->natts;
964963
parent_attno++)
@@ -969,7 +968,7 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
969968
ColumnDef *def;
970969

971970
/*
972-
* Ignore dropped columns in the parent. attmap entry is left zero.
971+
* Ignore dropped columns in the parent.
973972
*/
974973
if (attribute->attisdropped)
975974
continue;
@@ -1001,8 +1000,6 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
10011000
*/
10021001
cxt->columns = lappend(cxt->columns, def);
10031002

1004-
attmap[parent_attno - 1] = list_length(cxt->columns);
1005-
10061003
/*
10071004
* Copy default, if present and the default has been requested
10081005
*/
@@ -1082,22 +1079,126 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
10821079
/* We use oids if at least one LIKE'ed table has oids. */
10831080
cxt->hasoids |= relation->rd_rel->relhasoids;
10841081

1082+
/*
1083+
* We cannot yet deal with CHECK constraints or indexes, since we don't
1084+
* yet know what column numbers the copied columns will have in the
1085+
* 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.
1088+
*/
1089+
if (table_like_clause->options &
1090+
(CREATE_TABLE_LIKE_CONSTRAINTS |
1091+
CREATE_TABLE_LIKE_INDEXES))
1092+
cxt->alist = lappend(cxt->alist, table_like_clause);
1093+
1094+
/*
1095+
* We may copy extended statistics if requested, since the representation
1096+
* of CreateStatsStmt doesn't depend on column numbers.
1097+
*/
1098+
if (table_like_clause->options & CREATE_TABLE_LIKE_STATISTICS)
1099+
{
1100+
List *parent_extstats;
1101+
ListCell *l;
1102+
1103+
parent_extstats = RelationGetStatExtList(relation);
1104+
1105+
foreach(l, parent_extstats)
1106+
{
1107+
Oid parent_stat_oid = lfirst_oid(l);
1108+
CreateStatsStmt *stats_stmt;
1109+
1110+
stats_stmt = generateClonedExtStatsStmt(cxt->relation,
1111+
RelationGetRelid(relation),
1112+
parent_stat_oid);
1113+
1114+
/* Copy comment on statistics object, if requested */
1115+
if (table_like_clause->options & CREATE_TABLE_LIKE_COMMENTS)
1116+
{
1117+
comment = GetComment(parent_stat_oid, StatisticExtRelationId, 0);
1118+
1119+
/*
1120+
* We make use of CreateStatsStmt's stxcomment option, so as
1121+
* not to need to know now what name the statistics will have.
1122+
*/
1123+
stats_stmt->stxcomment = comment;
1124+
}
1125+
1126+
cxt->extstats = lappend(cxt->extstats, stats_stmt);
1127+
}
1128+
1129+
list_free(parent_extstats);
1130+
}
1131+
1132+
/*
1133+
* Close the parent rel, but keep our AccessShareLock on it until xact
1134+
* commit. That will prevent someone else from deleting or ALTERing the
1135+
* parent before we can run expandTableLikeClause.
1136+
*/
1137+
heap_close(relation, NoLock);
1138+
}
1139+
1140+
/*
1141+
* expandTableLikeClause
1142+
*
1143+
* Process LIKE options that require knowing the final column numbers
1144+
* assigned to the new table's columns. This executes after we have
1145+
* run DefineRelation for the new table. It returns a list of utility
1146+
* commands that should be run to generate indexes etc.
1147+
*/
1148+
List *
1149+
expandTableLikeClause(RangeVar *heapRel, TableLikeClause *table_like_clause)
1150+
{
1151+
List *result = NIL;
1152+
List *atsubcmds = NIL;
1153+
Relation relation;
1154+
Relation childrel;
1155+
TupleDesc tupleDesc;
1156+
TupleConstr *constr;
1157+
AttrNumber *attmap;
1158+
char *comment;
1159+
1160+
/*
1161+
* Open the relation referenced by the LIKE clause. We should still have
1162+
* the table lock obtained by transformTableLikeClause (and this'll throw
1163+
* an assertion failure if not). Hence, no need to recheck privileges
1164+
* etc.
1165+
*/
1166+
relation = relation_openrv(table_like_clause->relation, NoLock);
1167+
1168+
tupleDesc = RelationGetDescr(relation);
1169+
constr = tupleDesc->constr;
1170+
1171+
/*
1172+
* Open the newly-created child relation; we have lock on that too.
1173+
*/
1174+
childrel = relation_openrv(heapRel, NoLock);
1175+
1176+
/*
1177+
* Construct a map from the LIKE relation's attnos to the child rel's.
1178+
* This re-checks type match etc, although it shouldn't be possible to
1179+
* have a failure since both tables are locked.
1180+
*/
1181+
attmap = convert_tuples_by_name_map(RelationGetDescr(childrel),
1182+
tupleDesc,
1183+
gettext_noop("could not convert row type"));
1184+
10851185
/*
10861186
* Copy CHECK constraints if requested, being careful to adjust attribute
10871187
* numbers so they match the child.
10881188
*/
10891189
if ((table_like_clause->options & CREATE_TABLE_LIKE_CONSTRAINTS) &&
1090-
tupleDesc->constr)
1190+
constr != NULL)
10911191
{
10921192
int ccnum;
10931193

1094-
for (ccnum = 0; ccnum < tupleDesc->constr->num_check; ccnum++)
1194+
for (ccnum = 0; ccnum < constr->num_check; ccnum++)
10951195
{
1096-
char *ccname = tupleDesc->constr->check[ccnum].ccname;
1097-
char *ccbin = tupleDesc->constr->check[ccnum].ccbin;
1098-
Constraint *n = makeNode(Constraint);
1196+
char *ccname = constr->check[ccnum].ccname;
1197+
char *ccbin = constr->check[ccnum].ccbin;
10991198
Node *ccbin_node;
11001199
bool found_whole_row;
1200+
Constraint *n;
1201+
AlterTableCmd *atsubcmd;
11011202

11021203
ccbin_node = map_variable_attnos(stringToNode(ccbin),
11031204
1, 0,
@@ -1118,12 +1219,21 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
11181219
ccname,
11191220
RelationGetRelationName(relation))));
11201221

1222+
n = makeNode(Constraint);
11211223
n->contype = CONSTR_CHECK;
11221224
n->location = -1;
11231225
n->conname = pstrdup(ccname);
11241226
n->raw_expr = NULL;
11251227
n->cooked_expr = nodeToString(ccbin_node);
1126-
cxt->ckconstraints = lappend(cxt->ckconstraints, n);
1228+
1229+
/* We can skip validation, since the new table should be empty. */
1230+
n->skip_validation = true;
1231+
n->initially_valid = true;
1232+
1233+
atsubcmd = makeNode(AlterTableCmd);
1234+
atsubcmd->subtype = AT_AddConstraint;
1235+
atsubcmd->def = (Node *) n;
1236+
atsubcmds = lappend(atsubcmds, atsubcmd);
11271237

11281238
/* Copy comment on constraint */
11291239
if ((table_like_clause->options & CREATE_TABLE_LIKE_COMMENTS) &&
@@ -1135,18 +1245,34 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
11351245
CommentStmt *stmt = makeNode(CommentStmt);
11361246

11371247
stmt->objtype = OBJECT_TABCONSTRAINT;
1138-
stmt->object = (Node *) list_make3(makeString(cxt->relation->schemaname),
1139-
makeString(cxt->relation->relname),
1248+
stmt->object = (Node *) list_make3(makeString(heapRel->schemaname),
1249+
makeString(heapRel->relname),
11401250
makeString(n->conname));
11411251
stmt->comment = comment;
11421252

1143-
cxt->alist = lappend(cxt->alist, stmt);
1253+
result = lappend(result, stmt);
11441254
}
11451255
}
11461256
}
11471257

11481258
/*
1149-
* Likewise, copy indexes if requested
1259+
* If we generated any ALTER TABLE actions above, wrap them into a single
1260+
* ALTER TABLE command. Stick it at the front of the result, so it runs
1261+
* before any CommentStmts we made above.
1262+
*/
1263+
if (atsubcmds)
1264+
{
1265+
AlterTableStmt *atcmd = makeNode(AlterTableStmt);
1266+
1267+
atcmd->relation = copyObject(heapRel);
1268+
atcmd->cmds = atsubcmds;
1269+
atcmd->relkind = OBJECT_TABLE;
1270+
atcmd->missing_ok = false;
1271+
result = lcons(atcmd, result);
1272+
}
1273+
1274+
/*
1275+
* Process indexes if required.
11501276
*/
11511277
if ((table_like_clause->options & CREATE_TABLE_LIKE_INDEXES) &&
11521278
relation->rd_rel->relhasindex)
@@ -1165,7 +1291,7 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
11651291
parent_index = index_open(parent_index_oid, AccessShareLock);
11661292

11671293
/* Build CREATE INDEX statement to recreate the parent_index */
1168-
index_stmt = generateClonedIndexStmt(cxt->relation, InvalidOid,
1294+
index_stmt = generateClonedIndexStmt(heapRel, InvalidOid,
11691295
parent_index,
11701296
attmap, tupleDesc->natts, NULL);
11711297

@@ -1181,56 +1307,23 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
11811307
index_stmt->idxcomment = comment;
11821308
}
11831309

1184-
/* Save it in the inh_indexes list for the time being */
1185-
cxt->inh_indexes = lappend(cxt->inh_indexes, index_stmt);
1310+
result = lappend(result, index_stmt);
11861311

11871312
index_close(parent_index, AccessShareLock);
11881313
}
11891314
}
11901315

1191-
/*
1192-
* Likewise, copy extended statistics if requested
1193-
*/
1194-
if (table_like_clause->options & CREATE_TABLE_LIKE_STATISTICS)
1195-
{
1196-
List *parent_extstats;
1197-
ListCell *l;
1198-
1199-
parent_extstats = RelationGetStatExtList(relation);
1200-
1201-
foreach(l, parent_extstats)
1202-
{
1203-
Oid parent_stat_oid = lfirst_oid(l);
1204-
CreateStatsStmt *stats_stmt;
1205-
1206-
stats_stmt = generateClonedExtStatsStmt(cxt->relation,
1207-
RelationGetRelid(relation),
1208-
parent_stat_oid);
1209-
1210-
/* Copy comment on statistics object, if requested */
1211-
if (table_like_clause->options & CREATE_TABLE_LIKE_COMMENTS)
1212-
{
1213-
comment = GetComment(parent_stat_oid, StatisticExtRelationId, 0);
1214-
1215-
/*
1216-
* We make use of CreateStatsStmt's stxcomment option, so as
1217-
* not to need to know now what name the statistics will have.
1218-
*/
1219-
stats_stmt->stxcomment = comment;
1220-
}
1221-
1222-
cxt->extstats = lappend(cxt->extstats, stats_stmt);
1223-
}
1224-
1225-
list_free(parent_extstats);
1226-
}
1316+
/* Done with child rel */
1317+
heap_close(childrel, NoLock);
12271318

12281319
/*
12291320
* Close the parent rel, but keep our AccessShareLock on it until xact
12301321
* commit. That will prevent someone else from deleting or ALTERing the
12311322
* parent before the child is committed.
12321323
*/
12331324
heap_close(relation, NoLock);
1325+
1326+
return result;
12341327
}
12351328

12361329
static void
@@ -1810,24 +1903,6 @@ transformIndexConstraints(CreateStmtContext *cxt)
18101903
indexlist = lappend(indexlist, index);
18111904
}
18121905

1813-
/* Add in any indexes defined by LIKE ... INCLUDING INDEXES */
1814-
foreach(lc, cxt->inh_indexes)
1815-
{
1816-
index = (IndexStmt *) lfirst(lc);
1817-
1818-
if (index->primary)
1819-
{
1820-
if (cxt->pkey != NULL)
1821-
ereport(ERROR,
1822-
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
1823-
errmsg("multiple primary keys for table \"%s\" are not allowed",
1824-
cxt->relation->relname)));
1825-
cxt->pkey = index;
1826-
}
1827-
1828-
indexlist = lappend(indexlist, index);
1829-
}
1830-
18311906
/*
18321907
* Scan the index list and remove any redundant index specifications. This
18331908
* can happen if, for instance, the user writes UNIQUE PRIMARY KEY. A
@@ -2962,7 +3037,6 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
29623037
cxt.ckconstraints = NIL;
29633038
cxt.fkconstraints = NIL;
29643039
cxt.ixconstraints = NIL;
2965-
cxt.inh_indexes = NIL;
29663040
cxt.extstats = NIL;
29673041
cxt.blist = NIL;
29683042
cxt.alist = NIL;

0 commit comments

Comments
 (0)