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:
parent
359a3c586d
commit
d4e66a39eb
|
@ -561,6 +561,8 @@ static void ATPrepAlterColumnType(List **wqueue,
|
||||||
static bool ATColumnChangeRequiresRewrite(Node *expr, AttrNumber varattno);
|
static bool ATColumnChangeRequiresRewrite(Node *expr, AttrNumber varattno);
|
||||||
static ObjectAddress ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
|
static ObjectAddress ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
|
||||||
AlterTableCmd *cmd, LOCKMODE lockmode);
|
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 RememberConstraintForRebuilding(Oid conoid, AlteredTableInfo *tab);
|
||||||
static void RememberIndexForRebuilding(Oid indoid, AlteredTableInfo *tab);
|
static void RememberIndexForRebuilding(Oid indoid, AlteredTableInfo *tab);
|
||||||
static void RememberStatisticsForRebuilding(Oid stxoid, 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
|
* the info before executing ALTER TYPE, though, else the deparser will
|
||||||
* get confused.
|
* get confused.
|
||||||
*/
|
*/
|
||||||
depRel = table_open(DependRelationId, RowExclusiveLock);
|
RememberAllDependentForRebuilding(tab, rel, attnum, colName);
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Now scan for dependencies of this column on other things. The only
|
* Now scan for dependencies of this column on other things. The only
|
||||||
* things we should find are the dependency on the column datatype and
|
* things we should find are the dependency on the column datatype and
|
||||||
* possibly a collation dependency. Those can be removed.
|
* possibly a collation dependency. Those can be removed.
|
||||||
*/
|
*/
|
||||||
|
depRel = table_open(DependRelationId, RowExclusiveLock);
|
||||||
|
|
||||||
ScanKeyInit(&key[0],
|
ScanKeyInit(&key[0],
|
||||||
Anum_pg_depend_classid,
|
Anum_pg_depend_classid,
|
||||||
BTEqualStrategyNumber, F_OIDEQ,
|
BTEqualStrategyNumber, F_OIDEQ,
|
||||||
|
@ -13694,6 +13496,224 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
|
||||||
return address;
|
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
|
* Subroutine for ATExecAlterColumnType: remember that a replica identity
|
||||||
* needs to be reset.
|
* needs to be reset.
|
||||||
|
|
Loading…
Reference in New Issue