mirror of
https://github.com/postgres/postgres.git
synced 2025-04-24 10:47:04 +03:00
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 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.
|
||||
|
Loading…
x
Reference in New Issue
Block a user