| /*------------------------------------------------------------------------- |
| * |
| * pg_attrdef.c |
| * routines to support manipulation of the pg_attrdef relation |
| * |
| * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group |
| * Portions Copyright (c) 1994, Regents of the University of California |
| * |
| * |
| * IDENTIFICATION |
| * src/backend/catalog/pg_attrdef.c |
| * |
| *------------------------------------------------------------------------- |
| */ |
| #include "postgres.h" |
| |
| #include "access/genam.h" |
| #include "access/relation.h" |
| #include "access/table.h" |
| #include "catalog/catalog.h" |
| #include "catalog/dependency.h" |
| #include "catalog/indexing.h" |
| #include "catalog/objectaccess.h" |
| #include "catalog/oid_dispatch.h" |
| #include "catalog/pg_attrdef.h" |
| #include "executor/executor.h" |
| #include "optimizer/optimizer.h" |
| #include "utils/array.h" |
| #include "utils/builtins.h" |
| #include "utils/fmgroids.h" |
| #include "utils/rel.h" |
| #include "utils/syscache.h" |
| |
| |
| /* |
| * Store a default expression for column attnum of relation rel. |
| * |
| * Returns the OID of the new pg_attrdef tuple. |
| * |
| * add_column_mode must be true if we are storing the default for a new |
| * attribute, and false if it's for an already existing attribute. The reason |
| * for this is that the missing value must never be updated after it is set, |
| * which can only be when a column is added to the table. Otherwise we would |
| * in effect be changing existing tuples. |
| */ |
| Oid |
| StoreAttrDefault(Relation rel, AttrNumber attnum, |
| Node *expr, |
| bool *cookedMissingVal, |
| Datum *missingval_p, |
| bool *missingIsNull_p, |
| bool is_internal, bool add_column_mode) |
| { |
| char *adbin; |
| Relation adrel; |
| HeapTuple tuple; |
| Datum values[4]; |
| static bool nulls[4] = {false, false, false, false}; |
| Relation attrrel; |
| HeapTuple atttup; |
| Form_pg_attribute attStruct; |
| char attgenerated; |
| Oid attrdefOid; |
| ObjectAddress colobject, |
| defobject; |
| |
| adrel = table_open(AttrDefaultRelationId, RowExclusiveLock); |
| |
| /* |
| * Flatten expression to string form for storage. |
| */ |
| adbin = nodeToString(expr); |
| |
| /* |
| * Make the pg_attrdef entry. |
| */ |
| attrdefOid = GetNewOidForAttrDefault(adrel, AttrDefaultOidIndexId, |
| Anum_pg_attrdef_oid, |
| RelationGetRelid(rel), |
| attnum); |
| values[Anum_pg_attrdef_oid - 1] = ObjectIdGetDatum(attrdefOid); |
| values[Anum_pg_attrdef_adrelid - 1] = RelationGetRelid(rel); |
| values[Anum_pg_attrdef_adnum - 1] = attnum; |
| values[Anum_pg_attrdef_adbin - 1] = CStringGetTextDatum(adbin); |
| |
| tuple = heap_form_tuple(adrel->rd_att, values, nulls); |
| CatalogTupleInsert(adrel, tuple); |
| |
| defobject.classId = AttrDefaultRelationId; |
| defobject.objectId = attrdefOid; |
| defobject.objectSubId = 0; |
| |
| table_close(adrel, RowExclusiveLock); |
| |
| /* now can free some of the stuff allocated above */ |
| pfree(DatumGetPointer(values[Anum_pg_attrdef_adbin - 1])); |
| heap_freetuple(tuple); |
| pfree(adbin); |
| |
| /* |
| * Update the pg_attribute entry for the column to show that a default |
| * exists. |
| */ |
| attrrel = table_open(AttributeRelationId, RowExclusiveLock); |
| atttup = SearchSysCacheCopy2(ATTNUM, |
| ObjectIdGetDatum(RelationGetRelid(rel)), |
| Int16GetDatum(attnum)); |
| if (!HeapTupleIsValid(atttup)) |
| elog(ERROR, "cache lookup failed for attribute %d of relation %u", |
| attnum, RelationGetRelid(rel)); |
| attStruct = (Form_pg_attribute) GETSTRUCT(atttup); |
| attgenerated = attStruct->attgenerated; |
| if (!attStruct->atthasdef) |
| { |
| Form_pg_attribute defAttStruct; |
| |
| ExprState *exprState; |
| Expr *expr2 = (Expr *) expr; |
| EState *estate = NULL; |
| ExprContext *econtext; |
| Datum valuesAtt[Natts_pg_attribute] = {0}; |
| bool nullsAtt[Natts_pg_attribute] = {0}; |
| bool replacesAtt[Natts_pg_attribute] = {0}; |
| Datum missingval = (Datum) 0; |
| bool missingIsNull = true; |
| |
| valuesAtt[Anum_pg_attribute_atthasdef - 1] = true; |
| replacesAtt[Anum_pg_attribute_atthasdef - 1] = true; |
| |
| /* |
| * Note: this code is dead so far as core Postgres is concerned, |
| * because no caller passes add_column_mode = true anymore. We keep |
| * it in back branches on the slight chance that some extension is |
| * depending on it. |
| */ |
| if ((rel->rd_rel->relkind == RELKIND_RELATION || rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) && |
| add_column_mode && !attgenerated && cookedMissingVal && *cookedMissingVal) |
| { |
| missingval = *missingval_p; |
| missingIsNull = *missingIsNull_p; |
| } |
| else if ((rel->rd_rel->relkind == RELKIND_RELATION || rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) && |
| add_column_mode && !attgenerated) |
| { |
| expr2 = expression_planner(expr2); |
| estate = CreateExecutorState(); |
| exprState = ExecPrepareExpr(expr2, estate); |
| econtext = GetPerTupleExprContext(estate); |
| |
| missingval = ExecEvalExpr(exprState, econtext, |
| &missingIsNull); |
| |
| FreeExecutorState(estate); |
| |
| defAttStruct = TupleDescAttr(rel->rd_att, attnum - 1); |
| |
| if (missingIsNull) |
| { |
| /* if the default evaluates to NULL, just store a NULL array */ |
| missingval = (Datum) 0; |
| } |
| else |
| { |
| /* otherwise make a one-element array of the value */ |
| missingval = PointerGetDatum(construct_array(&missingval, |
| 1, |
| defAttStruct->atttypid, |
| defAttStruct->attlen, |
| defAttStruct->attbyval, |
| defAttStruct->attalign)); |
| } |
| |
| |
| } |
| if (add_column_mode && !attgenerated) |
| { |
| valuesAtt[Anum_pg_attribute_atthasmissing - 1] = !missingIsNull; |
| replacesAtt[Anum_pg_attribute_atthasmissing - 1] = true; |
| valuesAtt[Anum_pg_attribute_attmissingval - 1] = missingval; |
| replacesAtt[Anum_pg_attribute_attmissingval - 1] = true; |
| nullsAtt[Anum_pg_attribute_attmissingval - 1] = missingIsNull; |
| |
| *cookedMissingVal = true; |
| *missingval_p = missingval; |
| *missingIsNull_p = missingIsNull; |
| } |
| atttup = heap_modify_tuple(atttup, RelationGetDescr(attrrel), |
| valuesAtt, nullsAtt, replacesAtt); |
| |
| CatalogTupleUpdate(attrrel, &atttup->t_self, atttup); |
| |
| /* GPDB: don't free it, it's returned to the caller */ |
| // if (!missingIsNull) |
| // pfree(DatumGetPointer(missingval)); |
| } |
| table_close(attrrel, RowExclusiveLock); |
| heap_freetuple(atttup); |
| |
| /* |
| * Make a dependency so that the pg_attrdef entry goes away if the column |
| * (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, |
| attgenerated ? DEPENDENCY_INTERNAL : DEPENDENCY_AUTO); |
| |
| /* |
| * Record dependencies on objects used in the expression, too. |
| */ |
| recordDependencyOnSingleRelExpr(&defobject, expr, RelationGetRelid(rel), |
| DEPENDENCY_NORMAL, |
| DEPENDENCY_NORMAL, false); |
| |
| /* |
| * Post creation hook for attribute defaults. |
| * |
| * XXX. ALTER TABLE ALTER COLUMN SET/DROP DEFAULT is implemented with a |
| * couple of deletion/creation of the attribute's default entry, so the |
| * callee should check existence of an older version of this entry if it |
| * needs to distinguish. |
| */ |
| InvokeObjectPostCreateHookArg(AttrDefaultRelationId, |
| RelationGetRelid(rel), attnum, is_internal); |
| |
| return attrdefOid; |
| } |
| |
| |
| /* |
| * RemoveAttrDefault |
| * |
| * If the specified relation/attribute has a default, remove it. |
| * (If no default, raise error if complain is true, else return quietly.) |
| */ |
| void |
| RemoveAttrDefault(Oid relid, AttrNumber attnum, |
| DropBehavior behavior, bool complain, bool internal) |
| { |
| Relation attrdef_rel; |
| ScanKeyData scankeys[2]; |
| SysScanDesc scan; |
| HeapTuple tuple; |
| bool found = false; |
| |
| attrdef_rel = table_open(AttrDefaultRelationId, RowExclusiveLock); |
| |
| ScanKeyInit(&scankeys[0], |
| Anum_pg_attrdef_adrelid, |
| BTEqualStrategyNumber, F_OIDEQ, |
| ObjectIdGetDatum(relid)); |
| ScanKeyInit(&scankeys[1], |
| Anum_pg_attrdef_adnum, |
| BTEqualStrategyNumber, F_INT2EQ, |
| Int16GetDatum(attnum)); |
| |
| scan = systable_beginscan(attrdef_rel, AttrDefaultIndexId, true, |
| NULL, 2, scankeys); |
| |
| /* There should be at most one matching tuple, but we loop anyway */ |
| while (HeapTupleIsValid(tuple = systable_getnext(scan))) |
| { |
| ObjectAddress object; |
| Form_pg_attrdef attrtuple = (Form_pg_attrdef) GETSTRUCT(tuple); |
| |
| object.classId = AttrDefaultRelationId; |
| object.objectId = attrtuple->oid; |
| object.objectSubId = 0; |
| |
| performDeletion(&object, behavior, |
| internal ? PERFORM_DELETION_INTERNAL : 0); |
| |
| found = true; |
| } |
| |
| systable_endscan(scan); |
| table_close(attrdef_rel, RowExclusiveLock); |
| |
| if (complain && !found) |
| elog(ERROR, "could not find attrdef tuple for relation %u attnum %d", |
| relid, attnum); |
| } |
| |
| /* |
| * RemoveAttrDefaultById |
| * |
| * Remove a pg_attrdef entry specified by OID. This is the guts of |
| * attribute-default removal. Note it should be called via performDeletion, |
| * not directly. |
| */ |
| void |
| RemoveAttrDefaultById(Oid attrdefId) |
| { |
| Relation attrdef_rel; |
| Relation attr_rel; |
| Relation myrel; |
| ScanKeyData scankeys[1]; |
| SysScanDesc scan; |
| HeapTuple tuple; |
| Oid myrelid; |
| AttrNumber myattnum; |
| |
| /* Grab an appropriate lock on the pg_attrdef relation */ |
| attrdef_rel = table_open(AttrDefaultRelationId, RowExclusiveLock); |
| |
| /* Find the pg_attrdef tuple */ |
| ScanKeyInit(&scankeys[0], |
| Anum_pg_attrdef_oid, |
| BTEqualStrategyNumber, F_OIDEQ, |
| ObjectIdGetDatum(attrdefId)); |
| |
| scan = systable_beginscan(attrdef_rel, AttrDefaultOidIndexId, true, |
| NULL, 1, scankeys); |
| |
| tuple = systable_getnext(scan); |
| if (!HeapTupleIsValid(tuple)) |
| elog(ERROR, "could not find tuple for attrdef %u", attrdefId); |
| |
| myrelid = ((Form_pg_attrdef) GETSTRUCT(tuple))->adrelid; |
| myattnum = ((Form_pg_attrdef) GETSTRUCT(tuple))->adnum; |
| |
| /* Get an exclusive lock on the relation owning the attribute */ |
| myrel = relation_open(myrelid, AccessExclusiveLock); |
| |
| /* Now we can delete the pg_attrdef row */ |
| CatalogTupleDelete(attrdef_rel, &tuple->t_self); |
| |
| systable_endscan(scan); |
| table_close(attrdef_rel, RowExclusiveLock); |
| |
| /* Fix the pg_attribute row */ |
| attr_rel = table_open(AttributeRelationId, RowExclusiveLock); |
| |
| tuple = SearchSysCacheCopy2(ATTNUM, |
| ObjectIdGetDatum(myrelid), |
| Int16GetDatum(myattnum)); |
| if (!HeapTupleIsValid(tuple)) /* shouldn't happen */ |
| elog(ERROR, "cache lookup failed for attribute %d of relation %u", |
| myattnum, myrelid); |
| |
| ((Form_pg_attribute) GETSTRUCT(tuple))->atthasdef = false; |
| |
| CatalogTupleUpdate(attr_rel, &tuple->t_self, tuple); |
| |
| /* |
| * Our update of the pg_attribute row will force a relcache rebuild, so |
| * there's nothing else to do here. |
| */ |
| table_close(attr_rel, RowExclusiveLock); |
| |
| /* Keep lock on attribute's rel until end of xact */ |
| relation_close(myrel, NoLock); |
| } |
| |
| |
| /* |
| * Get the pg_attrdef OID of the default expression for a column |
| * identified by relation OID and column number. |
| * |
| * Returns InvalidOid if there is no such pg_attrdef entry. |
| */ |
| Oid |
| GetAttrDefaultOid(Oid relid, AttrNumber attnum) |
| { |
| Oid result = InvalidOid; |
| Relation attrdef; |
| ScanKeyData keys[2]; |
| SysScanDesc scan; |
| HeapTuple tup; |
| |
| attrdef = table_open(AttrDefaultRelationId, AccessShareLock); |
| ScanKeyInit(&keys[0], |
| Anum_pg_attrdef_adrelid, |
| BTEqualStrategyNumber, |
| F_OIDEQ, |
| ObjectIdGetDatum(relid)); |
| ScanKeyInit(&keys[1], |
| Anum_pg_attrdef_adnum, |
| BTEqualStrategyNumber, |
| F_INT2EQ, |
| Int16GetDatum(attnum)); |
| scan = systable_beginscan(attrdef, AttrDefaultIndexId, true, |
| NULL, 2, keys); |
| |
| if (HeapTupleIsValid(tup = systable_getnext(scan))) |
| { |
| Form_pg_attrdef atdform = (Form_pg_attrdef) GETSTRUCT(tup); |
| |
| result = atdform->oid; |
| } |
| |
| systable_endscan(scan); |
| table_close(attrdef, AccessShareLock); |
| |
| return result; |
| } |
| |
| /* |
| * Given a pg_attrdef OID, return the relation OID and column number of |
| * the owning column (represented as an ObjectAddress for convenience). |
| * |
| * Returns InvalidObjectAddress if there is no such pg_attrdef entry. |
| */ |
| ObjectAddress |
| GetAttrDefaultColumnAddress(Oid attrdefoid) |
| { |
| ObjectAddress result = InvalidObjectAddress; |
| Relation attrdef; |
| ScanKeyData skey[1]; |
| SysScanDesc scan; |
| HeapTuple tup; |
| |
| attrdef = table_open(AttrDefaultRelationId, AccessShareLock); |
| ScanKeyInit(&skey[0], |
| Anum_pg_attrdef_oid, |
| BTEqualStrategyNumber, F_OIDEQ, |
| ObjectIdGetDatum(attrdefoid)); |
| scan = systable_beginscan(attrdef, AttrDefaultOidIndexId, true, |
| NULL, 1, skey); |
| |
| if (HeapTupleIsValid(tup = systable_getnext(scan))) |
| { |
| Form_pg_attrdef atdform = (Form_pg_attrdef) GETSTRUCT(tup); |
| |
| result.classId = RelationRelationId; |
| result.objectId = atdform->adrelid; |
| result.objectSubId = atdform->adnum; |
| } |
| |
| systable_endscan(scan); |
| table_close(attrdef, AccessShareLock); |
| |
| return result; |
| } |