/*
* Make a dependency so that the pg_attrdef entry goes away if the column
- * (or whole table) is deleted.
+ * (or whole table) is deleted. In the case of a generated column, make
+ * it an internal dependency to prevent the default expression from being
+ * deleted separately.
*/
colobject.classId = RelationRelationId;
colobject.objectId = RelationGetRelid(rel);
colobject.objectSubId = attnum;
- recordDependencyOn(&defobject, &colobject, DEPENDENCY_AUTO);
+ recordDependencyOn(&defobject, &colobject,
+ attgenerated ? DEPENDENCY_INTERNAL : DEPENDENCY_AUTO);
/*
* Record dependencies on objects used in the expression, too.
*/
- if (attgenerated)
- {
- /*
- * Generated column: Dropping anything that the generation expression
- * refers to automatically drops the generated column.
- */
- recordDependencyOnSingleRelExpr(&colobject, expr, RelationGetRelid(rel),
- DEPENDENCY_AUTO,
- DEPENDENCY_AUTO, false);
- }
- else
- {
- /*
- * Normal default: Dropping anything that the default refers to
- * requires CASCADE and drops the default only.
- */
- recordDependencyOnSingleRelExpr(&defobject, expr, RelationGetRelid(rel),
- DEPENDENCY_NORMAL,
- DEPENDENCY_NORMAL, false);
- }
+ recordDependencyOnSingleRelExpr(&defobject, expr, RelationGetRelid(rel),
+ DEPENDENCY_NORMAL,
+ DEPENDENCY_NORMAL, false);
/*
* Post creation hook for attribute defaults.
Form_pg_attribute attTup;
AttrNumber attnum;
Relation attrelation;
+ Oid attrdefoid;
ObjectAddress address;
attrelation = table_open(AttributeRelationId, RowExclusiveLock);
}
}
+ /*
+ * Mark the column as no longer generated. (The atthasdef flag needs to
+ * get cleared too, but RemoveAttrDefault will handle that.)
+ */
attTup->attgenerated = '\0';
CatalogTupleUpdate(attrelation, &tuple->t_self, tuple);
InvokeObjectPostAlterHook(RelationRelationId,
RelationGetRelid(rel),
- attTup->attnum);
- ObjectAddressSubSet(address, RelationRelationId,
- RelationGetRelid(rel), attnum);
+ attnum);
heap_freetuple(tuple);
table_close(attrelation, RowExclusiveLock);
- CommandCounterIncrement();
-
- RemoveAttrDefault(RelationGetRelid(rel), attnum, DROP_RESTRICT, false, false);
-
/*
- * Remove all dependencies of this (formerly generated) column on other
- * columns in the same table. (See StoreAttrDefault() for which
- * dependencies are created.) We don't expect there to be dependencies
- * between columns of the same table for other reasons, so it's okay to
- * remove all of them.
+ * Drop the dependency records of the GENERATED expression, in particular
+ * its INTERNAL dependency on the column, which would otherwise cause
+ * dependency.c to refuse to perform the deletion.
*/
- {
- Relation depRel;
- ScanKeyData key[3];
- SysScanDesc scan;
- HeapTuple tup;
-
- depRel = table_open(DependRelationId, RowExclusiveLock);
-
- ScanKeyInit(&key[0],
- Anum_pg_depend_classid,
- BTEqualStrategyNumber, F_OIDEQ,
- ObjectIdGetDatum(RelationRelationId));
- ScanKeyInit(&key[1],
- Anum_pg_depend_objid,
- BTEqualStrategyNumber, F_OIDEQ,
- ObjectIdGetDatum(RelationGetRelid(rel)));
- ScanKeyInit(&key[2],
- Anum_pg_depend_objsubid,
- BTEqualStrategyNumber, F_INT4EQ,
- Int32GetDatum(attnum));
+ attrdefoid = GetAttrDefaultOid(RelationGetRelid(rel), attnum);
+ if (!OidIsValid(attrdefoid))
+ elog(ERROR, "could not find attrdef tuple for relation %u attnum %d",
+ RelationGetRelid(rel), attnum);
+ (void) deleteDependencyRecordsFor(AttrDefaultRelationId, attrdefoid, false);
- scan = systable_beginscan(depRel, DependDependerIndexId, true,
- NULL, 3, key);
-
- while (HeapTupleIsValid(tup = systable_getnext(scan)))
- {
- Form_pg_depend depform = (Form_pg_depend) GETSTRUCT(tup);
-
- if (depform->refclassid == RelationRelationId &&
- depform->refobjid == RelationGetRelid(rel) &&
- depform->refobjsubid != 0 &&
- depform->deptype == DEPENDENCY_AUTO)
- {
- CatalogTupleDelete(depRel, &tup->t_self);
- }
- }
-
- systable_endscan(scan);
+ /* Make above changes visible */
+ CommandCounterIncrement();
- table_close(depRel, RowExclusiveLock);
- }
+ /*
+ * Get rid of the GENERATED expression itself. We use RESTRICT here for
+ * safety, but at present we do not expect anything to depend on the
+ * default.
+ */
+ RemoveAttrDefault(RelationGetRelid(rel), attnum, DROP_RESTRICT,
+ false, false);
+ ObjectAddressSubSet(address, RelationRelationId,
+ RelationGetRelid(rel), attnum);
return address;
}
*/
Assert(foundObject.objectSubId == 0);
}
- else if (relKind == RELKIND_RELATION &&
- foundObject.objectSubId != 0 &&
- get_attgenerated(foundObject.objectId, foundObject.objectSubId))
- {
- /*
- * Changing the type of a column that is used by a
- * generated column is not allowed by SQL standard. It
- * might be doable with some thinking and effort.
- */
- ereport(ERROR,
- (errcode(ERRCODE_SYNTAX_ERROR),
- errmsg("cannot alter type of a column used by a generated column"),
- errdetail("Column \"%s\" is used by generated column \"%s\".",
- colName, get_attname(foundObject.objectId, foundObject.objectSubId, false))));
- }
else
{
/* Not expecting any other direct dependencies... */
break;
case OCLASS_DEFAULT:
+ {
+ ObjectAddress col = GetAttrDefaultColumnAddress(foundObject.objectId);
- /*
- * Ignore the column's default expression, since we will fix
- * it below.
- */
- Assert(defaultexpr);
- break;
+ 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:
/*
* Now scan for dependencies of this column on other things. The only
- * thing we should find is the dependency on the column datatype, which we
- * want to remove, possibly a collation dependency, and dependencies on
- * other columns if it is a generated column.
+ * things we should find are the dependency on the column datatype and
+ * possibly a collation dependency. Those can be removed.
*/
ScanKeyInit(&key[0],
Anum_pg_depend_classid,
foundObject.objectId = foundDep->refobjid;
foundObject.objectSubId = foundDep->refobjsubid;
- if (foundDep->deptype != DEPENDENCY_NORMAL &&
- foundDep->deptype != DEPENDENCY_AUTO)
+ if (foundDep->deptype != DEPENDENCY_NORMAL)
elog(ERROR, "found unexpected dependency type '%c'",
foundDep->deptype);
if (!(foundDep->refclassid == TypeRelationId &&
foundDep->refobjid == attTup->atttypid) &&
!(foundDep->refclassid == CollationRelationId &&
- foundDep->refobjid == attTup->attcollation) &&
- !(foundDep->refclassid == RelationRelationId &&
- foundDep->refobjid == RelationGetRelid(rel) &&
- foundDep->refobjsubid != 0)
- )
+ foundDep->refobjid == attTup->attcollation))
elog(ERROR, "found unexpected dependency for column: %s",
getObjectDescription(&foundObject, false));
*/
if (defaultexpr)
{
- /* Must make new row visible since it will be updated again */
+ /*
+ * If it's a GENERATED default, drop its dependency records, in
+ * particular its INTERNAL dependency on the column, which would
+ * otherwise cause dependency.c to refuse to perform the deletion.
+ */
+ if (attTup->attgenerated)
+ {
+ Oid attrdefoid = GetAttrDefaultOid(RelationGetRelid(rel), attnum);
+
+ if (!OidIsValid(attrdefoid))
+ elog(ERROR, "could not find attrdef tuple for relation %u attnum %d",
+ RelationGetRelid(rel), attnum);
+ (void) deleteDependencyRecordsFor(AttrDefaultRelationId, attrdefoid, false);
+ }
+
+ /*
+ * Make updates-so-far visible, particularly the new pg_attribute row
+ * which will be updated again.
+ */
CommandCounterIncrement();
/*