diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index efd6078f8b..b126116e25 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -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.