Refactor: separate function to find all objects depending on a column

Move code from ATExecAlterColumnType() that finds the all the objects
that depend on the column to a separate function.  A future patch will
reuse this code.

Author: Amul Sul <sulamul@gmail.com>
Discussion: https://www.postgresql.org/message-id/flat/CAAJ_b94yyJeGA-5M951_Lr+KfZokOp-2kXicpmEhi5FXhBeTog@mail.gmail.com
This commit is contained in:
Peter Eisentraut 2024-01-03 08:48:09 +01:00
parent 359a3c586d
commit d4e66a39eb
1 changed files with 223 additions and 203 deletions

View File

@ -561,6 +561,8 @@ static void ATPrepAlterColumnType(List **wqueue,
static bool ATColumnChangeRequiresRewrite(Node *expr, AttrNumber varattno);
static ObjectAddress ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
AlterTableCmd *cmd, LOCKMODE lockmode);
static void RememberAllDependentForRebuilding(AlteredTableInfo *tab,
Relation rel, AttrNumber attnum, const char *colName);
static void RememberConstraintForRebuilding(Oid conoid, AlteredTableInfo *tab);
static void RememberIndexForRebuilding(Oid indoid, AlteredTableInfo *tab);
static void RememberStatisticsForRebuilding(Oid stxoid, AlteredTableInfo *tab);
@ -13298,215 +13300,15 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
* the info before executing ALTER TYPE, though, else the deparser will
* get confused.
*/
depRel = table_open(DependRelationId, RowExclusiveLock);
ScanKeyInit(&key[0],
Anum_pg_depend_refclassid,
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(RelationRelationId));
ScanKeyInit(&key[1],
Anum_pg_depend_refobjid,
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(RelationGetRelid(rel)));
ScanKeyInit(&key[2],
Anum_pg_depend_refobjsubid,
BTEqualStrategyNumber, F_INT4EQ,
Int32GetDatum((int32) attnum));
scan = systable_beginscan(depRel, DependReferenceIndexId, true,
NULL, 3, key);
while (HeapTupleIsValid(depTup = systable_getnext(scan)))
{
Form_pg_depend foundDep = (Form_pg_depend) GETSTRUCT(depTup);
ObjectAddress foundObject;
foundObject.classId = foundDep->classid;
foundObject.objectId = foundDep->objid;
foundObject.objectSubId = foundDep->objsubid;
switch (getObjectClass(&foundObject))
{
case OCLASS_CLASS:
{
char relKind = get_rel_relkind(foundObject.objectId);
if (relKind == RELKIND_INDEX ||
relKind == RELKIND_PARTITIONED_INDEX)
{
Assert(foundObject.objectSubId == 0);
RememberIndexForRebuilding(foundObject.objectId, tab);
}
else if (relKind == RELKIND_SEQUENCE)
{
/*
* This must be a SERIAL column's sequence. We need
* not do anything to it.
*/
Assert(foundObject.objectSubId == 0);
}
else
{
/* Not expecting any other direct dependencies... */
elog(ERROR, "unexpected object depending on column: %s",
getObjectDescription(&foundObject, false));
}
break;
}
case OCLASS_CONSTRAINT:
Assert(foundObject.objectSubId == 0);
RememberConstraintForRebuilding(foundObject.objectId, tab);
break;
case OCLASS_REWRITE:
/* XXX someday see if we can cope with revising views */
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot alter type of a column used by a view or rule"),
errdetail("%s depends on column \"%s\"",
getObjectDescription(&foundObject, false),
colName)));
break;
case OCLASS_TRIGGER:
/*
* A trigger can depend on a column because the column is
* specified as an update target, or because the column is
* used in the trigger's WHEN condition. The first case would
* not require any extra work, but the second case would
* require updating the WHEN expression, which will take a
* significant amount of new code. Since we can't easily tell
* which case applies, we punt for both. FIXME someday.
*/
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot alter type of a column used in a trigger definition"),
errdetail("%s depends on column \"%s\"",
getObjectDescription(&foundObject, false),
colName)));
break;
case OCLASS_POLICY:
/*
* A policy can depend on a column because the column is
* specified in the policy's USING or WITH CHECK qual
* expressions. It might be possible to rewrite and recheck
* the policy expression, but punt for now. It's certainly
* easy enough to remove and recreate the policy; still, FIXME
* someday.
*/
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot alter type of a column used in a policy definition"),
errdetail("%s depends on column \"%s\"",
getObjectDescription(&foundObject, false),
colName)));
break;
case OCLASS_DEFAULT:
{
ObjectAddress col = GetAttrDefaultColumnAddress(foundObject.objectId);
if (col.objectId == RelationGetRelid(rel) &&
col.objectSubId == attnum)
{
/*
* Ignore the column's own default expression, which
* we will deal with below.
*/
Assert(defaultexpr);
}
else
{
/*
* This must be a reference from the expression of a
* generated column elsewhere in the same table.
* Changing the type of a column that is used by a
* generated column is not allowed by SQL standard, so
* just punt for now. It might be doable with some
* thinking and effort.
*/
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot alter type of a column used by a generated column"),
errdetail("Column \"%s\" is used by generated column \"%s\".",
colName,
get_attname(col.objectId,
col.objectSubId,
false))));
}
break;
}
case OCLASS_STATISTIC_EXT:
/*
* Give the extended-stats machinery a chance to fix anything
* that this column type change would break.
*/
RememberStatisticsForRebuilding(foundObject.objectId, tab);
break;
case OCLASS_PROC:
case OCLASS_TYPE:
case OCLASS_CAST:
case OCLASS_COLLATION:
case OCLASS_CONVERSION:
case OCLASS_LANGUAGE:
case OCLASS_LARGEOBJECT:
case OCLASS_OPERATOR:
case OCLASS_OPCLASS:
case OCLASS_OPFAMILY:
case OCLASS_AM:
case OCLASS_AMOP:
case OCLASS_AMPROC:
case OCLASS_SCHEMA:
case OCLASS_TSPARSER:
case OCLASS_TSDICT:
case OCLASS_TSTEMPLATE:
case OCLASS_TSCONFIG:
case OCLASS_ROLE:
case OCLASS_ROLE_MEMBERSHIP:
case OCLASS_DATABASE:
case OCLASS_TBLSPACE:
case OCLASS_FDW:
case OCLASS_FOREIGN_SERVER:
case OCLASS_USER_MAPPING:
case OCLASS_DEFACL:
case OCLASS_EXTENSION:
case OCLASS_EVENT_TRIGGER:
case OCLASS_PARAMETER_ACL:
case OCLASS_PUBLICATION:
case OCLASS_PUBLICATION_NAMESPACE:
case OCLASS_PUBLICATION_REL:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
/*
* We don't expect any of these sorts of objects to depend on
* a column.
*/
elog(ERROR, "unexpected object depending on column: %s",
getObjectDescription(&foundObject, false));
break;
/*
* There's intentionally no default: case here; we want the
* compiler to warn if a new OCLASS hasn't been handled above.
*/
}
}
systable_endscan(scan);
RememberAllDependentForRebuilding(tab, rel, attnum, colName);
/*
* Now scan for dependencies of this column on other things. The only
* things we should find are the dependency on the column datatype and
* possibly a collation dependency. Those can be removed.
*/
depRel = table_open(DependRelationId, RowExclusiveLock);
ScanKeyInit(&key[0],
Anum_pg_depend_classid,
BTEqualStrategyNumber, F_OIDEQ,
@ -13694,6 +13496,224 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
return address;
}
/*
* Subroutine for ATExecAlterColumnType: Find everything that depends on the
* column (constraints, indexes, etc), and record enough information to let us
* recreate the objects.
*/
static void
RememberAllDependentForRebuilding(AlteredTableInfo *tab, Relation rel, AttrNumber attnum, const char *colName)
{
Relation depRel;
ScanKeyData key[3];
SysScanDesc scan;
HeapTuple depTup;
depRel = table_open(DependRelationId, RowExclusiveLock);
ScanKeyInit(&key[0],
Anum_pg_depend_refclassid,
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(RelationRelationId));
ScanKeyInit(&key[1],
Anum_pg_depend_refobjid,
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(RelationGetRelid(rel)));
ScanKeyInit(&key[2],
Anum_pg_depend_refobjsubid,
BTEqualStrategyNumber, F_INT4EQ,
Int32GetDatum((int32) attnum));
scan = systable_beginscan(depRel, DependReferenceIndexId, true,
NULL, 3, key);
while (HeapTupleIsValid(depTup = systable_getnext(scan)))
{
Form_pg_depend foundDep = (Form_pg_depend) GETSTRUCT(depTup);
ObjectAddress foundObject;
foundObject.classId = foundDep->classid;
foundObject.objectId = foundDep->objid;
foundObject.objectSubId = foundDep->objsubid;
switch (getObjectClass(&foundObject))
{
case OCLASS_CLASS:
{
char relKind = get_rel_relkind(foundObject.objectId);
if (relKind == RELKIND_INDEX ||
relKind == RELKIND_PARTITIONED_INDEX)
{
Assert(foundObject.objectSubId == 0);
RememberIndexForRebuilding(foundObject.objectId, tab);
}
else if (relKind == RELKIND_SEQUENCE)
{
/*
* This must be a SERIAL column's sequence. We need
* not do anything to it.
*/
Assert(foundObject.objectSubId == 0);
}
else
{
/* Not expecting any other direct dependencies... */
elog(ERROR, "unexpected object depending on column: %s",
getObjectDescription(&foundObject, false));
}
break;
}
case OCLASS_CONSTRAINT:
Assert(foundObject.objectSubId == 0);
RememberConstraintForRebuilding(foundObject.objectId, tab);
break;
case OCLASS_REWRITE:
/* XXX someday see if we can cope with revising views */
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot alter type of a column used by a view or rule"),
errdetail("%s depends on column \"%s\"",
getObjectDescription(&foundObject, false),
colName)));
break;
case OCLASS_TRIGGER:
/*
* A trigger can depend on a column because the column is
* specified as an update target, or because the column is
* used in the trigger's WHEN condition. The first case would
* not require any extra work, but the second case would
* require updating the WHEN expression, which will take a
* significant amount of new code. Since we can't easily tell
* which case applies, we punt for both. FIXME someday.
*/
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot alter type of a column used in a trigger definition"),
errdetail("%s depends on column \"%s\"",
getObjectDescription(&foundObject, false),
colName)));
break;
case OCLASS_POLICY:
/*
* A policy can depend on a column because the column is
* specified in the policy's USING or WITH CHECK qual
* expressions. It might be possible to rewrite and recheck
* the policy expression, but punt for now. It's certainly
* easy enough to remove and recreate the policy; still, FIXME
* someday.
*/
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot alter type of a column used in a policy definition"),
errdetail("%s depends on column \"%s\"",
getObjectDescription(&foundObject, false),
colName)));
break;
case OCLASS_DEFAULT:
{
ObjectAddress col = GetAttrDefaultColumnAddress(foundObject.objectId);
if (col.objectId == RelationGetRelid(rel) &&
col.objectSubId == attnum)
{
/*
* Ignore the column's own default expression. The
* caller deals with it.
*/
}
else
{
/*
* This must be a reference from the expression of a
* generated column elsewhere in the same table.
* Changing the type of a column that is used by a
* generated column is not allowed by SQL standard, so
* just punt for now. It might be doable with some
* thinking and effort.
*/
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot alter type of a column used by a generated column"),
errdetail("Column \"%s\" is used by generated column \"%s\".",
colName,
get_attname(col.objectId,
col.objectSubId,
false))));
}
break;
}
case OCLASS_STATISTIC_EXT:
/*
* Give the extended-stats machinery a chance to fix anything
* that this column type change would break.
*/
RememberStatisticsForRebuilding(foundObject.objectId, tab);
break;
case OCLASS_PROC:
case OCLASS_TYPE:
case OCLASS_CAST:
case OCLASS_COLLATION:
case OCLASS_CONVERSION:
case OCLASS_LANGUAGE:
case OCLASS_LARGEOBJECT:
case OCLASS_OPERATOR:
case OCLASS_OPCLASS:
case OCLASS_OPFAMILY:
case OCLASS_AM:
case OCLASS_AMOP:
case OCLASS_AMPROC:
case OCLASS_SCHEMA:
case OCLASS_TSPARSER:
case OCLASS_TSDICT:
case OCLASS_TSTEMPLATE:
case OCLASS_TSCONFIG:
case OCLASS_ROLE:
case OCLASS_ROLE_MEMBERSHIP:
case OCLASS_DATABASE:
case OCLASS_TBLSPACE:
case OCLASS_FDW:
case OCLASS_FOREIGN_SERVER:
case OCLASS_USER_MAPPING:
case OCLASS_DEFACL:
case OCLASS_EXTENSION:
case OCLASS_EVENT_TRIGGER:
case OCLASS_PARAMETER_ACL:
case OCLASS_PUBLICATION:
case OCLASS_PUBLICATION_NAMESPACE:
case OCLASS_PUBLICATION_REL:
case OCLASS_SUBSCRIPTION:
case OCLASS_TRANSFORM:
/*
* We don't expect any of these sorts of objects to depend on
* a column.
*/
elog(ERROR, "unexpected object depending on column: %s",
getObjectDescription(&foundObject, false));
break;
/*
* There's intentionally no default: case here; we want the
* compiler to warn if a new OCLASS hasn't been handled above.
*/
}
}
systable_endscan(scan);
table_close(depRel, NoLock);
}
/*
* Subroutine for ATExecAlterColumnType: remember that a replica identity
* needs to be reset.