Skip to content

Commit 5028981

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 eabba4a commit 5028981

File tree

9 files changed

+435
-175
lines changed

9 files changed

+435
-175
lines changed

src/backend/commands/tablecmds.c

+105-21
Original file line numberDiff line numberDiff line change
@@ -405,6 +405,8 @@ static bool ConstraintImpliedByRelConstraint(Relation scanrel,
405405
List *testConstraint, List *provenConstraint);
406406
static ObjectAddress ATExecColumnDefault(Relation rel, const char *colName,
407407
Node *newDefault, LOCKMODE lockmode);
408+
static ObjectAddress ATExecCookedColumnDefault(Relation rel, AttrNumber attnum,
409+
Node *newDefault);
408410
static ObjectAddress ATExecAddIdentity(Relation rel, const char *colName,
409411
Node *def, LOCKMODE lockmode);
410412
static ObjectAddress ATExecSetIdentity(Relation rel, const char *colName,
@@ -2054,8 +2056,8 @@ storage_name(char c)
20542056
* 'schema' is the column/attribute definition for the table. (It's a list
20552057
* of ColumnDef's.) It is destructively changed.
20562058
* 'supers' is a list of OIDs of parent relations, already locked by caller.
2057-
* 'relpersistence' is a persistence type of the table.
2058-
* 'is_partition' tells if the table is a partition
2059+
* 'relpersistence' is the persistence type of the table.
2060+
* 'is_partition' tells if the table is a partition.
20592061
*
20602062
* Output arguments:
20612063
* 'supconstr' receives a list of constraints belonging to the parents,
@@ -2218,7 +2220,11 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
22182220
TupleDesc tupleDesc;
22192221
TupleConstr *constr;
22202222
AttrMap *newattmap;
2223+
List *inherited_defaults;
2224+
List *cols_with_defaults;
22212225
AttrNumber parent_attno;
2226+
ListCell *lc1;
2227+
ListCell *lc2;
22222228

22232229
/* caller already got lock */
22242230
relation = table_open(parent, NoLock);
@@ -2304,6 +2310,9 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
23042310
*/
23052311
newattmap = make_attrmap(tupleDesc->natts);
23062312

2313+
/* We can't process inherited defaults until newattmap is complete. */
2314+
inherited_defaults = cols_with_defaults = NIL;
2315+
23072316
for (parent_attno = 1; parent_attno <= tupleDesc->natts;
23082317
parent_attno++)
23092318
{
@@ -2359,7 +2368,7 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
23592368
get_collation_name(defCollId),
23602369
get_collation_name(attribute->attcollation))));
23612370

2362-
/* Copy storage parameter */
2371+
/* Copy/check storage parameter */
23632372
if (def->storage == 0)
23642373
def->storage = attribute->attstorage;
23652374
else if (def->storage != attribute->attstorage)
@@ -2410,7 +2419,7 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
24102419
}
24112420

24122421
/*
2413-
* Copy default if any
2422+
* Locate default if any
24142423
*/
24152424
if (attribute->atthasdef)
24162425
{
@@ -2432,23 +2441,59 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
24322441
Assert(this_default != NULL);
24332442

24342443
/*
2435-
* If default expr could contain any vars, we'd need to fix
2436-
* 'em, but it can't; so default is ready to apply to child.
2437-
*
2438-
* If we already had a default from some prior parent, check
2439-
* to see if they are the same. If so, no problem; if not,
2440-
* mark the column as having a bogus default. Below, we will
2441-
* complain if the bogus default isn't overridden by the child
2442-
* schema.
2444+
* If it's a GENERATED default, it might contain Vars that
2445+
* need to be mapped to the inherited column(s)' new numbers.
2446+
* We can't do that till newattmap is ready, so just remember
2447+
* all the inherited default expressions for the moment.
24432448
*/
2444-
Assert(def->raw_default == NULL);
2445-
if (def->cooked_default == NULL)
2446-
def->cooked_default = this_default;
2447-
else if (!equal(def->cooked_default, this_default))
2448-
{
2449-
def->cooked_default = &bogus_marker;
2450-
have_bogus_defaults = true;
2451-
}
2449+
inherited_defaults = lappend(inherited_defaults, this_default);
2450+
cols_with_defaults = lappend(cols_with_defaults, def);
2451+
}
2452+
}
2453+
2454+
/*
2455+
* Now process any inherited default expressions, adjusting attnos
2456+
* using the completed newattmap map.
2457+
*/
2458+
forboth(lc1, inherited_defaults, lc2, cols_with_defaults)
2459+
{
2460+
Node *this_default = (Node *) lfirst(lc1);
2461+
ColumnDef *def = (ColumnDef *) lfirst(lc2);
2462+
bool found_whole_row;
2463+
2464+
/* Adjust Vars to match new table's column numbering */
2465+
this_default = map_variable_attnos(this_default,
2466+
1, 0,
2467+
newattmap,
2468+
InvalidOid, &found_whole_row);
2469+
2470+
/*
2471+
* For the moment we have to reject whole-row variables. We could
2472+
* convert them, if we knew the new table's rowtype OID, but that
2473+
* hasn't been assigned yet. (A variable could only appear in a
2474+
* generation expression, so the error message is correct.)
2475+
*/
2476+
if (found_whole_row)
2477+
ereport(ERROR,
2478+
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
2479+
errmsg("cannot convert whole-row table reference"),
2480+
errdetail("Generation expression for column \"%s\" contains a whole-row reference to table \"%s\".",
2481+
def->colname,
2482+
RelationGetRelationName(relation))));
2483+
2484+
/*
2485+
* If we already had a default from some prior parent, check to
2486+
* see if they are the same. If so, no problem; if not, mark the
2487+
* column as having a bogus default. Below, we will complain if
2488+
* the bogus default isn't overridden by the child schema.
2489+
*/
2490+
Assert(def->raw_default == NULL);
2491+
if (def->cooked_default == NULL)
2492+
def->cooked_default = this_default;
2493+
else if (!equal(def->cooked_default, this_default))
2494+
{
2495+
def->cooked_default = &bogus_marker;
2496+
have_bogus_defaults = true;
24522497
}
24532498
}
24542499

@@ -2667,7 +2712,6 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
26672712
def->raw_default = newdef->raw_default;
26682713
def->cooked_default = newdef->cooked_default;
26692714
}
2670-
26712715
}
26722716
else
26732717
{
@@ -3781,6 +3825,7 @@ AlterTableGetLockLevel(List *cmds)
37813825
* Theoretically, these could be ShareRowExclusiveLock.
37823826
*/
37833827
case AT_ColumnDefault:
3828+
case AT_CookedColumnDefault:
37843829
case AT_AlterConstraint:
37853830
case AT_AddIndex: /* from ADD CONSTRAINT */
37863831
case AT_AddIndexConstraint:
@@ -4040,6 +4085,13 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
40404085
/* No command-specific prep needed */
40414086
pass = cmd->def ? AT_PASS_ADD_OTHERCONSTR : AT_PASS_DROP;
40424087
break;
4088+
case AT_CookedColumnDefault: /* add a pre-cooked default */
4089+
/* This is currently used only in CREATE TABLE */
4090+
/* (so the permission check really isn't necessary) */
4091+
ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
4092+
/* This command never recurses */
4093+
pass = AT_PASS_ADD_OTHERCONSTR;
4094+
break;
40434095
case AT_AddIdentity:
40444096
ATSimplePermissions(rel, ATT_TABLE | ATT_VIEW | ATT_FOREIGN_TABLE);
40454097
/* This command never recurses */
@@ -4398,6 +4450,9 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
43984450
case AT_ColumnDefault: /* ALTER COLUMN DEFAULT */
43994451
address = ATExecColumnDefault(rel, cmd->name, cmd->def, lockmode);
44004452
break;
4453+
case AT_CookedColumnDefault: /* add a pre-cooked default */
4454+
address = ATExecCookedColumnDefault(rel, cmd->num, cmd->def);
4455+
break;
44014456
case AT_AddIdentity:
44024457
cmd = ATParseTransformCmd(wqueue, tab, rel, cmd, false, lockmode,
44034458
cur_pass, context);
@@ -6859,6 +6914,35 @@ ATExecColumnDefault(Relation rel, const char *colName,
68596914
return address;
68606915
}
68616916

6917+
/*
6918+
* Add a pre-cooked default expression.
6919+
*
6920+
* Return the address of the affected column.
6921+
*/
6922+
static ObjectAddress
6923+
ATExecCookedColumnDefault(Relation rel, AttrNumber attnum,
6924+
Node *newDefault)
6925+
{
6926+
ObjectAddress address;
6927+
6928+
/* We assume no checking is required */
6929+
6930+
/*
6931+
* Remove any old default for the column. We use RESTRICT here for
6932+
* safety, but at present we do not expect anything to depend on the
6933+
* default. (In ordinary cases, there could not be a default in place
6934+
* anyway, but it's possible when combining LIKE with inheritance.)
6935+
*/
6936+
RemoveAttrDefault(RelationGetRelid(rel), attnum, DROP_RESTRICT, false,
6937+
true);
6938+
6939+
(void) StoreAttrDefault(rel, attnum, newDefault, true, false);
6940+
6941+
ObjectAddressSubSet(address, RelationRelationId,
6942+
RelationGetRelid(rel), attnum);
6943+
return address;
6944+
}
6945+
68626946
/*
68636947
* ALTER TABLE ALTER COLUMN ADD IDENTITY
68646948
*

0 commit comments

Comments
 (0)