From 058f3422536dfffe027201a59366d830c4ab7675 Mon Sep 17 00:00:00 2001
From: Greg Burd <greg@burd.me>
Date: Fri, 14 Nov 2025 08:02:31 -0500
Subject: [PATCH v2 1/3] Refactor how we form HeapTuples for
 CatalogTuple(Insert|Update)

NOTE: this is a work-in-progress, not commitable as it is. In htup.h
there are two sets of macros that change how we insert/update catalog
tuples.  The newer, proposed version are toward the top of the file
while the older v1 macros follow a "TODO" comment. The idea was not to
convert the entirety until the pattern is acceptable so as to avoid
unnecessary work.

This commit provides a set of macros that should be used when inserting
or updating tuples.  These macros help declare state necessary when
tracking updates and forming new tuples.  There are macros used to set
fields to new values or to NULL.  Finally there are macros for inserting
or updating these tuples.

There are a few reasons to standardize catalog modification code and
move it away from direct Form/GETSTRUCT or values/nulls/replaces-style
access.  First, these models can be error prone and require attention to
a variety of details that can easily be overlooked.  Second, at present
while we know what fields are modified we don't preserve that
information and pass it on to the heap_update() code.  Third, at some
point we'll need in-memory representations completely decoupled from the
disk to support upgradeable catalogs.

Previous to this patch there were two methods for accomplishing this
task; Form/GETSTRUCT, and values/nulls/replaces. This new approach does
not change the fundamentals of this process but instead attempts to
cover up the details so as to make it a bit more intuitive and less
error-prone.  These changes are, for the most part, backward compatible.

It is now possible to retain knowledge of the set of mutated attributes
when working with catalog tuples.  A follow-on patch will change
simple_heap_update() to use the updated bitmaps gathered during catalog
tuple modification so as to avoid having to call
HeapDetermineColumnsInfo().  This prevents catlog updates from having to
re-discover the set of modified attributes by comparing old/new
HeapTuple Datums while holding a buffer lock when trying to identify
indexed attributes that have new values and should prevent HOT updates.

The "Form/GETSTRUCT" model allows for direct access to the tuple data
that is then modified, copied, and then updated via
ModifyCatalogTupleUpdate() which will write the modified catalog tuple
into the heap relation.

Old:
  Form_pg_index form = (Form_pg_index) GETSTRUCT(tuple);

  form->inisclustered = false;
  CatalogTupleUpdate(relation, &tuple->t_self, tuple);

New:
  CatalogUpdateFormContext(pg_index, ctx);
  CatalogSetForm(pg_index, ctx, tuple);

  CatalogTupleUpdateField(ctx, pg_index, indisclustered, false);
  ModifyCatalogTupleForm(relation, tuple, ctx);

The "values/nulls/replaces" model collects the necessary information to
either update or create a heap tuple using heap_modify_tuple() or
heap_form_tuple() or sometimes heap_modify_tuple_by_cols().  While all
those functions remain unchanged and can be used it is simpler to use
the ModifyCatalogTupleValues() macro to write the changed heap tuple
into the catalog relation.

Old:
  bool    nulls[Natts_pg_type];
  bool    replaces[Natts_pg_type];
  Datum   values[Natts_pg_type];

  values[Anum_pg_type_typtype - 1] = CharGetDatum(typeType);
  nulls[Anum_pg_type_typdefaultbin - 1] = true;
  replaces[Anum_pg_type_oid - 1] = false;

  tup = heap_modify_tuple(tuple, desc, values, nulls, replaces);

  CatalogTupleUpdate(relation, &tuple->t_self, tuple);

New:
  CatalogUpdateValuesContext(pg_type, ctx);

  CatalogTupleUpdateValue(ctx, pg_type, typtype, CharGetDatum(typeType));
  ModifyCatalogTupleValues(relation, tuple, ctx);

The heap_update_tuple() function is functionally equivalent to
heap_modify_tuple(), but takes a Bitmapset called "updated" rather than
an array of bool generally called "replaces" as a method for indicating
what was modified.  Additionally, this new function tries to balance the
tradeoffs of calling heap_getattr() versus heap_deform_tuple() based
on the ratio of attributes updated and their known runtime complexities.
Both paths are functionally equivalent.

The changes also include initialization of the values/nulls arrays
rather than depending on loops or memset().
---
 src/backend/access/common/heaptuple.c    | 119 +++++++++++
 src/backend/catalog/indexing.c           | 137 +++++++------
 src/backend/catalog/pg_aggregate.c       |  74 +++----
 src/backend/catalog/pg_constraint.c      | 161 +++++++--------
 src/backend/commands/alter.c             |  55 +++--
 src/backend/commands/analyze.c           |  84 +++-----
 src/backend/statistics/attribute_stats.c | 194 +++++++-----------
 src/include/access/htup.h                | 250 +++++++++++++++++++++++
 src/include/access/htup_details.h        |   8 +
 src/include/catalog/indexing.h           |  17 +-
 10 files changed, 704 insertions(+), 395 deletions(-)

diff --git a/src/backend/access/common/heaptuple.c b/src/backend/access/common/heaptuple.c
index 74a52d87067..364f03b043e 100644
--- a/src/backend/access/common/heaptuple.c
+++ b/src/backend/access/common/heaptuple.c
@@ -1325,6 +1325,125 @@ heap_modify_tuple_by_cols(HeapTuple tuple,
 	return newTuple;
 }
 
+/*
+ * heap_update_tuple
+ *		Form a new tuple from an old tuple and a set of replacement values.
+ *
+ * Creates a new HeapTuple by selectively replacing attributes from the original
+ * tuple with new values. The 'updated' Bitmapset specifies which attributes
+ * (by attribute number, 1-based adjusted by FirstLowInvalidHeapAttributeNumber)
+ * should be replaced with corresponding entries from new_values and new_isnull
+ * arrays (0-based indices).
+ *
+ * Performance strategy:
+ * - If updating many attributes (> 2*natts/3), use heap_getattr() to extract
+ *   only the few non-updated attributes. This is O(k*n) where k is the number
+ *   of non-updated attributes, which is small when updating many.
+ * - If updating few attributes (<= 2*natts/3), use heap_deform_tuple() to
+ *   extract all attributes at once (O(n)), then replace the updated ones.
+ *   This avoids the O(n^2) cost of many heap_getattr() calls.
+ *
+ * The threshold of 2*natts/3 balances the fixed O(n) cost of heap_deform_tuple
+ * against the variable O(k*n) cost of heap_getattr, where k = natts - num_updated.
+ */
+HeapTuple
+heap_update_tuple(HeapTuple tuple,
+				  TupleDesc desc,
+				  const Datum *new_values,
+				  const bool *new_nulls,
+				  const Bitmapset *updated)
+{
+	int			natts = desc->natts;
+	int			num_updated;
+	Datum	   *values;
+	bool	   *nulls;
+	HeapTuple	new_tuple;
+
+	Assert(!bms_is_empty(updated));
+
+	num_updated = bms_num_members(updated);
+	Assert(num_updated <= natts);
+
+	values = (Datum *) palloc0(natts * sizeof(Datum));
+	nulls = (bool *) palloc0(natts * sizeof(bool));
+
+	/*
+	 * Choose strategy based on update density. When updating most attributes,
+	 * it's cheaper to extract the few unchanged ones individually.
+	 */
+	if (num_updated > (2 * natts) / 3)
+	{
+		/* Updating many: use heap_getattr for the few non-updated attributes */
+		for (int i = 0; i < natts; i++)
+		{
+			/*
+			 * Convert array index to attribute number, then to bitmapset
+			 * member. Array index i (0-based) -> attnum (1-based) -> bms
+			 * member.
+			 */
+			AttrNumber	attnum = i + 1;
+			int			member = attnum - FirstLowInvalidHeapAttributeNumber;
+
+			if (bms_is_member(member, updated))
+			{
+				/* Use replacement value */
+				if (unlikely(!new_values || !new_nulls))
+					values[i] = heap_getattr(tuple, attnum, desc, &nulls[i]);
+
+				if (likely(new_values))
+					values[i] = new_values[i];
+
+				if (likely(new_nulls))
+					nulls[i] = new_nulls[i];
+			}
+			else
+			{
+				/* Extract original value using heap_getattr (1-based) */
+				values[i] = heap_getattr(tuple, attnum, desc, &nulls[i]);
+			}
+		}
+	}
+	else
+	{
+		int			member = -1;
+
+		/* Updating few: deform entire tuple, then replace updated attributes */
+		heap_deform_tuple(tuple, desc, values, nulls);
+
+		while ((member = bms_next_member(updated, member)) >= 0)
+		{
+			/*
+			 * Convert bitmapset member to attribute number, then to array
+			 * index. bms_member -> attnum (1-based) -> array index i
+			 * (0-based).
+			 */
+			AttrNumber	attnum = member + FirstLowInvalidHeapAttributeNumber;
+			int			i = attnum - 1;
+
+			Assert(i >= 0 && i < natts);
+
+			if (likely(new_values))
+				values[i] = new_values[i];
+
+			if (likely(new_nulls))
+				nulls[i] = new_nulls[i];
+		}
+	}
+
+	/* Create the new tuple */
+	new_tuple = heap_form_tuple(desc, values, nulls);
+
+	pfree(values);
+	pfree(nulls);
+
+	/* Preserve tuple identity and location information from the original */
+	new_tuple->t_data->t_ctid = tuple->t_data->t_ctid;
+	new_tuple->t_self = tuple->t_self;
+	new_tuple->t_tableOid = tuple->t_tableOid;
+
+	return new_tuple;
+}
+
 /*
  * heap_deform_tuple
  *		Given a tuple, extract data into values/isnull arrays; this is
diff --git a/src/backend/catalog/indexing.c b/src/backend/catalog/indexing.c
index 004c5121000..51efac85f65 100644
--- a/src/backend/catalog/indexing.c
+++ b/src/backend/catalog/indexing.c
@@ -225,58 +225,67 @@ CatalogTupleCheckConstraints(Relation heapRel, HeapTuple tup)
  *
  * This is a convenience routine for the common case of inserting a single
  * tuple in a system catalog; it inserts a new heap tuple, keeping indexes
- * current.  Avoid using it for multiple tuples, since opening the indexes
- * and building the index info structures is moderately expensive.
- * (Use CatalogTupleInsertWithInfo in such cases.)
+ * current.
+ *
+ * If 'indstate' is NULL, the function opens and closes the indexes internally.
+ * This is convenient for single-tuple updates but has overhead from opening the
+ * indexes and building index info structures.
+ *
+ * If 'indstate' is provided (non-NULL), it is used directly without opening or
+ * closing indexes. This allows callers to amortize index management costs across
+ * multiple tuple updates. Callers must use CatalogOpenIndexes() before the first
+ * update and CatalogCloseIndexes() after the last update.
+ *
+ * XXX: At some point we might cache the CatalogIndexState data somewhere (perhaps
+ * in the relcache) so that callers needn't trouble over this.
  */
 void
-CatalogTupleInsert(Relation heapRel, HeapTuple tup)
+CatalogTupleInsert(Relation heapRel, HeapTuple tup,
+				   CatalogIndexState indstate)
 {
-	CatalogIndexState indstate;
-
-	CatalogTupleCheckConstraints(heapRel, tup);
-
-	indstate = CatalogOpenIndexes(heapRel);
+	bool		close_indexes = false;
 
-	simple_heap_insert(heapRel, tup);
-
-	CatalogIndexInsert(indstate, tup, TU_All);
-	CatalogCloseIndexes(indstate);
-}
+	/* Open indexes if not provided by caller */
+	if (indstate == NULL)
+	{
+		indstate = CatalogOpenIndexes(heapRel);
+		close_indexes = true;
+	}
 
-/*
- * CatalogTupleInsertWithInfo - as above, but with caller-supplied index info
- *
- * This should be used when it's important to amortize CatalogOpenIndexes/
- * CatalogCloseIndexes work across multiple insertions.  At some point we
- * might cache the CatalogIndexState data somewhere (perhaps in the relcache)
- * so that callers needn't trouble over this ... but we don't do so today.
- */
-void
-CatalogTupleInsertWithInfo(Relation heapRel, HeapTuple tup,
-						   CatalogIndexState indstate)
-{
 	CatalogTupleCheckConstraints(heapRel, tup);
 
 	simple_heap_insert(heapRel, tup);
 
 	CatalogIndexInsert(indstate, tup, TU_All);
+
+	/* Close indexes only if we opened them ourselves */
+	if (close_indexes)
+		CatalogCloseIndexes(indstate);
 }
 
 /*
- * CatalogTuplesMultiInsertWithInfo - as above, but for multiple tuples
+ * CatalogTuplesMultiInsert - as above, but for multiple tuples
  *
  * Insert multiple tuples into the given catalog relation at once, with an
  * amortized cost of CatalogOpenIndexes.
  */
 void
-CatalogTuplesMultiInsertWithInfo(Relation heapRel, TupleTableSlot **slot,
-								 int ntuples, CatalogIndexState indstate)
+CatalogTuplesMultiInsert(Relation heapRel, TupleTableSlot **slot,
+						 int ntuples, CatalogIndexState indstate)
 {
+	bool		close_indexes = false;
+
 	/* Nothing to do */
 	if (ntuples <= 0)
 		return;
 
+	/* Open indexes if not provided by caller */
+	if (indstate == NULL)
+	{
+		indstate = CatalogOpenIndexes(heapRel);
+		close_indexes = true;
+	}
+
 	heap_multi_insert(heapRel, slot, ntuples,
 					  GetCurrentCommandId(true), 0, NULL);
 
@@ -296,6 +305,10 @@ CatalogTuplesMultiInsertWithInfo(Relation heapRel, TupleTableSlot **slot,
 		if (should_free)
 			heap_freetuple(tuple);
 	}
+
+	/* Close indexes only if we opened them ourselves */
+	if (close_indexes)
+		CatalogCloseIndexes(indstate);
 }
 
 /*
@@ -303,47 +316,44 @@ CatalogTuplesMultiInsertWithInfo(Relation heapRel, TupleTableSlot **slot,
  *
  * Update the tuple identified by "otid", replacing it with the data in "tup".
  *
- * This is a convenience routine for the common case of updating a single
- * tuple in a system catalog; it updates one heap tuple, keeping indexes
- * current.  Avoid using it for multiple tuples, since opening the indexes
- * and building the index info structures is moderately expensive.
- * (Use CatalogTupleUpdateWithInfo in such cases.)
+ * This function updates a heap tuple in a system catalog and keeps its indexes
+ * current. The 'updated' bitmapset specifies which columns were modified.
+ *
+ * If 'indstate' is NULL, the function opens and closes the indexes internally.
+ * This is convenient for single-tuple updates but has overhead from opening the
+ * indexes and building index info structures.
+ *
+ * If 'indstate' is provided (non-NULL), it is used directly without opening or
+ * closing indexes. This allows callers to amortize index management costs across
+ * multiple tuple updates. Callers must use CatalogOpenIndexes() before the first
+ * update and CatalogCloseIndexes() after the last update.
+ *
+ * XXX: At some point we might cache the CatalogIndexState data somewhere (perhaps
+ * in the relcache) so that callers needn't trouble over this.
  */
 void
-CatalogTupleUpdate(Relation heapRel, const ItemPointerData *otid, HeapTuple tup)
+CatalogTupleUpdate(Relation heapRel, const ItemPointerData *otid, HeapTuple tuple,
+				   const Bitmapset *updated, CatalogIndexState indstate)
 {
-	CatalogIndexState indstate;
 	TU_UpdateIndexes updateIndexes = TU_All;
+	bool		close_indexes = false;
 
-	CatalogTupleCheckConstraints(heapRel, tup);
-
-	indstate = CatalogOpenIndexes(heapRel);
-
-	simple_heap_update(heapRel, otid, tup, &updateIndexes);
-
-	CatalogIndexInsert(indstate, tup, updateIndexes);
-	CatalogCloseIndexes(indstate);
-}
+	CatalogTupleCheckConstraints(heapRel, tuple);
 
-/*
- * CatalogTupleUpdateWithInfo - as above, but with caller-supplied index info
- *
- * This should be used when it's important to amortize CatalogOpenIndexes/
- * CatalogCloseIndexes work across multiple updates.  At some point we
- * might cache the CatalogIndexState data somewhere (perhaps in the relcache)
- * so that callers needn't trouble over this ... but we don't do so today.
- */
-void
-CatalogTupleUpdateWithInfo(Relation heapRel, const ItemPointerData *otid, HeapTuple tup,
-						   CatalogIndexState indstate)
-{
-	TU_UpdateIndexes updateIndexes = TU_All;
+	/* Open indexes if not provided by caller */
+	if (indstate == NULL)
+	{
+		indstate = CatalogOpenIndexes(heapRel);
+		close_indexes = true;
+	}
 
-	CatalogTupleCheckConstraints(heapRel, tup);
+	simple_heap_update(heapRel, otid, tuple, &updateIndexes);
 
-	simple_heap_update(heapRel, otid, tup, &updateIndexes);
+	CatalogIndexInsert(indstate, tuple, updateIndexes);
 
-	CatalogIndexInsert(indstate, tup, updateIndexes);
+	/* Close indexes only if we opened them ourselves */
+	if (close_indexes)
+		CatalogCloseIndexes(indstate);
 }
 
 /*
@@ -355,11 +365,6 @@ CatalogTupleUpdateWithInfo(Relation heapRel, const ItemPointerData *otid, HeapTu
  * cleanup will be done later by VACUUM.  However, callers of this function
  * shouldn't have to know that; we'd like a uniform abstraction for all
  * catalog tuple changes.  Hence, provide this currently-trivial wrapper.
- *
- * The abstraction is a bit leaky in that we don't provide an optimized
- * CatalogTupleDeleteWithInfo version, because there is currently nothing to
- * optimize.  If we ever need that, rather than touching a lot of call sites,
- * it might be better to do something about caching CatalogIndexState.
  */
 void
 CatalogTupleDelete(Relation heapRel, const ItemPointerData *tid)
diff --git a/src/backend/catalog/pg_aggregate.c b/src/backend/catalog/pg_aggregate.c
index a1cb5719a0c..0519241cbf0 100644
--- a/src/backend/catalog/pg_aggregate.c
+++ b/src/backend/catalog/pg_aggregate.c
@@ -79,9 +79,6 @@ AggregateCreate(const char *aggName,
 	Relation	aggdesc;
 	HeapTuple	tup;
 	HeapTuple	oldtup;
-	bool		nulls[Natts_pg_aggregate];
-	Datum		values[Natts_pg_aggregate];
-	bool		replaces[Natts_pg_aggregate];
 	Form_pg_proc proc;
 	Oid			transfn;
 	Oid			finalfn = InvalidOid;	/* can be omitted */
@@ -100,14 +97,14 @@ AggregateCreate(const char *aggName,
 	int			nargs_transfn;
 	int			nargs_finalfn;
 	Oid			procOid;
-	TupleDesc	tupDesc;
 	char	   *detailmsg;
-	int			i;
 	ObjectAddress myself,
 				referenced;
 	ObjectAddresses *addrs;
 	AclResult	aclresult;
 
+	CatalogUpdateValuesContext(pg_aggregate, ctx);
+
 	/* sanity checks (caller should have caught these) */
 	if (!aggName)
 		elog(ERROR, "no aggregate name supplied");
@@ -584,7 +581,7 @@ AggregateCreate(const char *aggName,
 	/*
 	 * permission checks on used types
 	 */
-	for (i = 0; i < numArgs; i++)
+	for (int i = 0; i < numArgs; i++)
 	{
 		aclresult = object_aclcheck(TypeRelationId, aggArgTypes[i], GetUserId(), ACL_USAGE);
 		if (aclresult != ACLCHECK_OK)
@@ -648,43 +645,36 @@ AggregateCreate(const char *aggName,
 	 * Okay to create the pg_aggregate entry.
 	 */
 	aggdesc = table_open(AggregateRelationId, RowExclusiveLock);
-	tupDesc = aggdesc->rd_att;
 
 	/* initialize nulls and values */
-	for (i = 0; i < Natts_pg_aggregate; i++)
-	{
-		nulls[i] = false;
-		values[i] = (Datum) 0;
-		replaces[i] = true;
-	}
-	values[Anum_pg_aggregate_aggfnoid - 1] = ObjectIdGetDatum(procOid);
-	values[Anum_pg_aggregate_aggkind - 1] = CharGetDatum(aggKind);
-	values[Anum_pg_aggregate_aggnumdirectargs - 1] = Int16GetDatum(numDirectArgs);
-	values[Anum_pg_aggregate_aggtransfn - 1] = ObjectIdGetDatum(transfn);
-	values[Anum_pg_aggregate_aggfinalfn - 1] = ObjectIdGetDatum(finalfn);
-	values[Anum_pg_aggregate_aggcombinefn - 1] = ObjectIdGetDatum(combinefn);
-	values[Anum_pg_aggregate_aggserialfn - 1] = ObjectIdGetDatum(serialfn);
-	values[Anum_pg_aggregate_aggdeserialfn - 1] = ObjectIdGetDatum(deserialfn);
-	values[Anum_pg_aggregate_aggmtransfn - 1] = ObjectIdGetDatum(mtransfn);
-	values[Anum_pg_aggregate_aggminvtransfn - 1] = ObjectIdGetDatum(minvtransfn);
-	values[Anum_pg_aggregate_aggmfinalfn - 1] = ObjectIdGetDatum(mfinalfn);
-	values[Anum_pg_aggregate_aggfinalextra - 1] = BoolGetDatum(finalfnExtraArgs);
-	values[Anum_pg_aggregate_aggmfinalextra - 1] = BoolGetDatum(mfinalfnExtraArgs);
-	values[Anum_pg_aggregate_aggfinalmodify - 1] = CharGetDatum(finalfnModify);
-	values[Anum_pg_aggregate_aggmfinalmodify - 1] = CharGetDatum(mfinalfnModify);
-	values[Anum_pg_aggregate_aggsortop - 1] = ObjectIdGetDatum(sortop);
-	values[Anum_pg_aggregate_aggtranstype - 1] = ObjectIdGetDatum(aggTransType);
-	values[Anum_pg_aggregate_aggtransspace - 1] = Int32GetDatum(aggTransSpace);
-	values[Anum_pg_aggregate_aggmtranstype - 1] = ObjectIdGetDatum(aggmTransType);
-	values[Anum_pg_aggregate_aggmtransspace - 1] = Int32GetDatum(aggmTransSpace);
+	CatalogTupleUpdateValue(ctx, pg_aggregate, aggfnoid, ObjectIdGetDatum(procOid));
+	CatalogTupleUpdateValue(ctx, pg_aggregate, aggkind, CharGetDatum(aggKind));
+	CatalogTupleUpdateValue(ctx, pg_aggregate, aggnumdirectargs, Int16GetDatum(numDirectArgs));
+	CatalogTupleUpdateValue(ctx, pg_aggregate, aggtransfn, ObjectIdGetDatum(transfn));
+	CatalogTupleUpdateValue(ctx, pg_aggregate, aggfinalfn, ObjectIdGetDatum(finalfn));
+	CatalogTupleUpdateValue(ctx, pg_aggregate, aggcombinefn, ObjectIdGetDatum(combinefn));
+	CatalogTupleUpdateValue(ctx, pg_aggregate, aggserialfn, ObjectIdGetDatum(serialfn));
+	CatalogTupleUpdateValue(ctx, pg_aggregate, aggdeserialfn, ObjectIdGetDatum(deserialfn));
+	CatalogTupleUpdateValue(ctx, pg_aggregate, aggmtransfn, ObjectIdGetDatum(mtransfn));
+	CatalogTupleUpdateValue(ctx, pg_aggregate, aggminvtransfn, ObjectIdGetDatum(minvtransfn));
+	CatalogTupleUpdateValue(ctx, pg_aggregate, aggmfinalfn, ObjectIdGetDatum(mfinalfn));
+	CatalogTupleUpdateValue(ctx, pg_aggregate, aggfinalextra, BoolGetDatum(finalfnExtraArgs));
+	CatalogTupleUpdateValue(ctx, pg_aggregate, aggmfinalextra, BoolGetDatum(mfinalfnExtraArgs));
+	CatalogTupleUpdateValue(ctx, pg_aggregate, aggfinalmodify, CharGetDatum(finalfnModify));
+	CatalogTupleUpdateValue(ctx, pg_aggregate, aggmfinalmodify, CharGetDatum(mfinalfnModify));
+	CatalogTupleUpdateValue(ctx, pg_aggregate, aggsortop, ObjectIdGetDatum(sortop));
+	CatalogTupleUpdateValue(ctx, pg_aggregate, aggtranstype, ObjectIdGetDatum(aggTransType));
+	CatalogTupleUpdateValue(ctx, pg_aggregate, aggtransspace, Int32GetDatum(aggTransSpace));
+	CatalogTupleUpdateValue(ctx, pg_aggregate, aggmtranstype, ObjectIdGetDatum(aggmTransType));
+	CatalogTupleUpdateValue(ctx, pg_aggregate, aggmtransspace, Int32GetDatum(aggmTransSpace));
 	if (agginitval)
-		values[Anum_pg_aggregate_agginitval - 1] = CStringGetTextDatum(agginitval);
+		CatalogTupleUpdateValue(ctx, pg_aggregate, agginitval, CStringGetTextDatum(agginitval));
 	else
-		nulls[Anum_pg_aggregate_agginitval - 1] = true;
+		CatalogTupleUpdateValueNull(ctx, pg_aggregate, agginitval);
 	if (aggminitval)
-		values[Anum_pg_aggregate_aggminitval - 1] = CStringGetTextDatum(aggminitval);
+		CatalogTupleUpdateValue(ctx, pg_aggregate, aggminitval, CStringGetTextDatum(aggminitval));
 	else
-		nulls[Anum_pg_aggregate_aggminitval - 1] = true;
+		CatalogTupleUpdateValueNull(ctx, pg_aggregate, aggminitval);
 
 	if (replace)
 		oldtup = SearchSysCache1(AGGFNOID, ObjectIdGetDatum(procOid));
@@ -717,18 +707,12 @@ AggregateCreate(const char *aggName,
 					(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
 					 errmsg("cannot change number of direct arguments of an aggregate function")));
 
-		replaces[Anum_pg_aggregate_aggfnoid - 1] = false;
-		replaces[Anum_pg_aggregate_aggkind - 1] = false;
-		replaces[Anum_pg_aggregate_aggnumdirectargs - 1] = false;
-
-		tup = heap_modify_tuple(oldtup, tupDesc, values, nulls, replaces);
-		CatalogTupleUpdate(aggdesc, &tup->t_self, tup);
+		ModifyCatalogTupleValues(aggdesc, oldtup, ctx);
 		ReleaseSysCache(oldtup);
 	}
 	else
 	{
-		tup = heap_form_tuple(tupDesc, values, nulls);
-		CatalogTupleInsert(aggdesc, tup);
+		InsertCatalogTupleValues(aggdesc, ctx);
 	}
 
 	table_close(aggdesc, RowExclusiveLock);
diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c
index 9944e4bd2d1..d09daad0b1b 100644
--- a/src/backend/catalog/pg_constraint.c
+++ b/src/backend/catalog/pg_constraint.c
@@ -84,9 +84,6 @@ CreateConstraintEntry(const char *constraintName,
 {
 	Relation	conDesc;
 	Oid			conOid;
-	HeapTuple	tup;
-	bool		nulls[Natts_pg_constraint];
-	Datum		values[Natts_pg_constraint];
 	ArrayType  *conkeyArray;
 	ArrayType  *confkeyArray;
 	ArrayType  *conpfeqopArray;
@@ -100,6 +97,8 @@ CreateConstraintEntry(const char *constraintName,
 	ObjectAddresses *addrs_auto;
 	ObjectAddresses *addrs_normal;
 
+	CatalogInsertValuesContext(pg_constraint, ctx);
+
 	/* Only CHECK or FOREIGN KEY constraint can be not enforced */
 	Assert(isEnforced || constraintType == CONSTRAINT_CHECK ||
 		   constraintType == CONSTRAINT_FOREIGN);
@@ -175,79 +174,70 @@ CreateConstraintEntry(const char *constraintName,
 	else
 		conexclopArray = NULL;
 
-	/* initialize nulls and values */
-	for (i = 0; i < Natts_pg_constraint; i++)
-	{
-		nulls[i] = false;
-		values[i] = (Datum) 0;
-	}
-
 	conOid = GetNewOidWithIndex(conDesc, ConstraintOidIndexId,
 								Anum_pg_constraint_oid);
-	values[Anum_pg_constraint_oid - 1] = ObjectIdGetDatum(conOid);
-	values[Anum_pg_constraint_conname - 1] = NameGetDatum(&cname);
-	values[Anum_pg_constraint_connamespace - 1] = ObjectIdGetDatum(constraintNamespace);
-	values[Anum_pg_constraint_contype - 1] = CharGetDatum(constraintType);
-	values[Anum_pg_constraint_condeferrable - 1] = BoolGetDatum(isDeferrable);
-	values[Anum_pg_constraint_condeferred - 1] = BoolGetDatum(isDeferred);
-	values[Anum_pg_constraint_conenforced - 1] = BoolGetDatum(isEnforced);
-	values[Anum_pg_constraint_convalidated - 1] = BoolGetDatum(isValidated);
-	values[Anum_pg_constraint_conrelid - 1] = ObjectIdGetDatum(relId);
-	values[Anum_pg_constraint_contypid - 1] = ObjectIdGetDatum(domainId);
-	values[Anum_pg_constraint_conindid - 1] = ObjectIdGetDatum(indexRelId);
-	values[Anum_pg_constraint_conparentid - 1] = ObjectIdGetDatum(parentConstrId);
-	values[Anum_pg_constraint_confrelid - 1] = ObjectIdGetDatum(foreignRelId);
-	values[Anum_pg_constraint_confupdtype - 1] = CharGetDatum(foreignUpdateType);
-	values[Anum_pg_constraint_confdeltype - 1] = CharGetDatum(foreignDeleteType);
-	values[Anum_pg_constraint_confmatchtype - 1] = CharGetDatum(foreignMatchType);
-	values[Anum_pg_constraint_conislocal - 1] = BoolGetDatum(conIsLocal);
-	values[Anum_pg_constraint_coninhcount - 1] = Int16GetDatum(conInhCount);
-	values[Anum_pg_constraint_connoinherit - 1] = BoolGetDatum(conNoInherit);
-	values[Anum_pg_constraint_conperiod - 1] = BoolGetDatum(conPeriod);
+	CatalogTupleSetValue(ctx, pg_constraint, oid, ObjectIdGetDatum(conOid));
+	CatalogTupleSetValue(ctx, pg_constraint, conname, NameGetDatum(&cname));
+	CatalogTupleSetValue(ctx, pg_constraint, connamespace, ObjectIdGetDatum(constraintNamespace));
+	CatalogTupleSetValue(ctx, pg_constraint, contype, CharGetDatum(constraintType));
+	CatalogTupleSetValue(ctx, pg_constraint, condeferrable, BoolGetDatum(isDeferrable));
+	CatalogTupleSetValue(ctx, pg_constraint, condeferred, BoolGetDatum(isDeferred));
+	CatalogTupleSetValue(ctx, pg_constraint, conenforced, BoolGetDatum(isEnforced));
+	CatalogTupleSetValue(ctx, pg_constraint, convalidated, BoolGetDatum(isValidated));
+	CatalogTupleSetValue(ctx, pg_constraint, conrelid, ObjectIdGetDatum(relId));
+	CatalogTupleSetValue(ctx, pg_constraint, contypid, ObjectIdGetDatum(domainId));
+	CatalogTupleSetValue(ctx, pg_constraint, conindid, ObjectIdGetDatum(indexRelId));
+	CatalogTupleSetValue(ctx, pg_constraint, conparentid, ObjectIdGetDatum(parentConstrId));
+	CatalogTupleSetValue(ctx, pg_constraint, confrelid, ObjectIdGetDatum(foreignRelId));
+	CatalogTupleSetValue(ctx, pg_constraint, confupdtype, CharGetDatum(foreignUpdateType));
+	CatalogTupleSetValue(ctx, pg_constraint, confdeltype, CharGetDatum(foreignDeleteType));
+	CatalogTupleSetValue(ctx, pg_constraint, confmatchtype, CharGetDatum(foreignMatchType));
+	CatalogTupleSetValue(ctx, pg_constraint, conislocal, BoolGetDatum(conIsLocal));
+	CatalogTupleSetValue(ctx, pg_constraint, coninhcount, Int16GetDatum(conInhCount));
+	CatalogTupleSetValue(ctx, pg_constraint, connoinherit, BoolGetDatum(conNoInherit));
+	CatalogTupleSetValue(ctx, pg_constraint, conperiod, BoolGetDatum(conPeriod));
 
 	if (conkeyArray)
-		values[Anum_pg_constraint_conkey - 1] = PointerGetDatum(conkeyArray);
+		CatalogTupleSetValue(ctx, pg_constraint, conkey, PointerGetDatum(conkeyArray));
 	else
-		nulls[Anum_pg_constraint_conkey - 1] = true;
+		CatalogTupleSetValueNull(ctx, pg_constraint, conkey);
 
 	if (confkeyArray)
-		values[Anum_pg_constraint_confkey - 1] = PointerGetDatum(confkeyArray);
+		CatalogTupleSetValue(ctx, pg_constraint, confkey, PointerGetDatum(confkeyArray));
 	else
-		nulls[Anum_pg_constraint_confkey - 1] = true;
+		CatalogTupleSetValueNull(ctx, pg_constraint, confkey);
 
 	if (conpfeqopArray)
-		values[Anum_pg_constraint_conpfeqop - 1] = PointerGetDatum(conpfeqopArray);
+		CatalogTupleSetValue(ctx, pg_constraint, conpfeqop, PointerGetDatum(conpfeqopArray));
 	else
-		nulls[Anum_pg_constraint_conpfeqop - 1] = true;
+		CatalogTupleSetValueNull(ctx, pg_constraint, conpfeqop);
 
 	if (conppeqopArray)
-		values[Anum_pg_constraint_conppeqop - 1] = PointerGetDatum(conppeqopArray);
+		CatalogTupleSetValue(ctx, pg_constraint, conppeqop, PointerGetDatum(conppeqopArray));
 	else
-		nulls[Anum_pg_constraint_conppeqop - 1] = true;
+		CatalogTupleSetValueNull(ctx, pg_constraint, conppeqop);
 
 	if (conffeqopArray)
-		values[Anum_pg_constraint_conffeqop - 1] = PointerGetDatum(conffeqopArray);
+		CatalogTupleSetValue(ctx, pg_constraint, conffeqop, PointerGetDatum(conffeqopArray));
 	else
-		nulls[Anum_pg_constraint_conffeqop - 1] = true;
+		CatalogTupleSetValueNull(ctx, pg_constraint, conffeqop);
 
 	if (confdelsetcolsArray)
-		values[Anum_pg_constraint_confdelsetcols - 1] = PointerGetDatum(confdelsetcolsArray);
+		CatalogTupleSetValue(ctx, pg_constraint, confdelsetcols, PointerGetDatum(confdelsetcolsArray));
 	else
-		nulls[Anum_pg_constraint_confdelsetcols - 1] = true;
+		CatalogTupleSetValueNull(ctx, pg_constraint, confdelsetcols);
 
 	if (conexclopArray)
-		values[Anum_pg_constraint_conexclop - 1] = PointerGetDatum(conexclopArray);
+		CatalogTupleSetValue(ctx, pg_constraint, conexclop, PointerGetDatum(conexclopArray));
 	else
-		nulls[Anum_pg_constraint_conexclop - 1] = true;
+		CatalogTupleSetValueNull(ctx, pg_constraint, conexclop);
 
 	if (conBin)
-		values[Anum_pg_constraint_conbin - 1] = CStringGetTextDatum(conBin);
+		CatalogTupleSetValue(ctx, pg_constraint, conbin, CStringGetTextDatum(conBin));
 	else
-		nulls[Anum_pg_constraint_conbin - 1] = true;
+		CatalogTupleSetValueNull(ctx, pg_constraint, conbin);
 
-	tup = heap_form_tuple(RelationGetDescr(conDesc), values, nulls);
-
-	CatalogTupleInsert(conDesc, tup);
+	InsertCatalogTupleValues(conDesc, ctx);
 
 	ObjectAddressSet(conobject, ConstraintRelationId, conOid);
 
@@ -747,21 +737,21 @@ AdjustNotNullInheritance(Oid relid, AttrNumber attnum,
 	if (HeapTupleIsValid(tup))
 	{
 		Relation	pg_constraint;
-		Form_pg_constraint conform;
-		bool		changed = false;
+
+		CatalogUpdateFormContext(pg_constraint, ctx);
+		CatalogSetForm(pg_constraint, ctx, tup);
 
 		pg_constraint = table_open(ConstraintRelationId, RowExclusiveLock);
-		conform = (Form_pg_constraint) GETSTRUCT(tup);
 
 		/*
 		 * If the NO INHERIT flag we're asked for doesn't match what the
 		 * existing constraint has, throw an error.
 		 */
-		if (is_no_inherit != conform->connoinherit)
+		if (is_no_inherit != CatalogGetFormField(ctx, connoinherit))
 			ereport(ERROR,
 					errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 					errmsg("cannot change NO INHERIT status of NOT NULL constraint \"%s\" on relation \"%s\"",
-						   NameStr(conform->conname), get_rel_name(relid)),
+						   NameStr(CatalogGetFormField(ctx, conname)), get_rel_name(relid)),
 					errhint("You might need to make the existing constraint inheritable using %s.",
 							"ALTER TABLE ... ALTER CONSTRAINT ... INHERIT"));
 
@@ -769,31 +759,29 @@ AdjustNotNullInheritance(Oid relid, AttrNumber attnum,
 		 * Throw an error if the existing constraint is NOT VALID and caller
 		 * wants a valid one.
 		 */
-		if (!is_notvalid && !conform->convalidated)
+		if (!is_notvalid && !CatalogGetFormField(ctx, convalidated))
 			ereport(ERROR,
 					errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 					errmsg("incompatible NOT VALID constraint \"%s\" on relation \"%s\"",
-						   NameStr(conform->conname), get_rel_name(relid)),
+						   NameStr(CatalogGetFormField(ctx, conname)), get_rel_name(relid)),
 					errhint("You might need to validate it using %s.",
 							"ALTER TABLE ... VALIDATE CONSTRAINT"));
 
 		if (!is_local)
 		{
-			if (pg_add_s16_overflow(conform->coninhcount, 1,
-									&conform->coninhcount))
-				ereport(ERROR,
-						errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
-						errmsg("too many inheritance parents"));
-			changed = true;
+			CatalogTupleCondUpdateValue(ctx, pg_constraint, coninhcount,
+										pg_add_s16_overflow(CatalogGetFormField(ctx, coninhcount), 1,
+															&CatalogGetFormField(ctx, coninhcount)),
+										ereport(ERROR, errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+												errmsg("too many inheritance parents")));
 		}
-		else if (!conform->conislocal)
+		else if (!CatalogGetFormField(ctx, conislocal))
 		{
-			conform->conislocal = true;
-			changed = true;
+			CatalogTupleUpdateField(ctx, pg_constraint, conislocal, true);
 		}
 
-		if (changed)
-			CatalogTupleUpdate(pg_constraint, &tup->t_self, tup);
+		if (CatalogTupleHasChanged(ctx))
+			UpdateCatalogTupleForm(pg_constraint, tup, ctx);
 
 		table_close(pg_constraint, RowExclusiveLock);
 
@@ -928,6 +916,7 @@ RemoveConstraintById(Oid conId)
 			Relation	pgrel;
 			HeapTuple	relTup;
 			Form_pg_class classForm;
+			Bitmapset  *updated = NULL;
 
 			pgrel = table_open(RelationRelationId, RowExclusiveLock);
 			relTup = SearchSysCacheCopy1(RELOID,
@@ -938,14 +927,14 @@ RemoveConstraintById(Oid conId)
 			classForm = (Form_pg_class) GETSTRUCT(relTup);
 
 			if (classForm->relchecks > 0)
-				classForm->relchecks--;
+				HeapTupleUpdateField(pg_class, relchecks, classForm->relchecks - 1, classForm, updated);
 			else
 				/* should not happen */
 				elog(WARNING, "relation \"%s\" has relchecks = %d",
 					 RelationGetRelationName(rel), classForm->relchecks);
 
-			CatalogTupleUpdate(pgrel, &relTup->t_self, relTup);
-
+			CatalogTupleUpdate(pgrel, &relTup->t_self, relTup, updated, NULL);
+			bms_free(updated);
 			heap_freetuple(relTup);
 
 			table_close(pgrel, RowExclusiveLock);
@@ -990,6 +979,7 @@ RenameConstraintById(Oid conId, const char *newname)
 	Relation	conDesc;
 	HeapTuple	tuple;
 	Form_pg_constraint con;
+	Bitmapset  *updated = NULL;
 
 	conDesc = table_open(ConstraintRelationId, RowExclusiveLock);
 
@@ -1020,11 +1010,13 @@ RenameConstraintById(Oid conId, const char *newname)
 
 	/* OK, do the rename --- tuple is a copy, so OK to scribble on it */
 	namestrcpy(&(con->conname), newname);
+	HeapTupleMarkColumnUpdated(pg_constraint, conname, updated);
 
-	CatalogTupleUpdate(conDesc, &tuple->t_self, tuple);
+	CatalogTupleUpdate(conDesc, &tuple->t_self, tuple, updated, NULL);
 
 	InvokeObjectPostAlterHook(ConstraintRelationId, conId, 0);
 
+	bms_free(updated);
 	heap_freetuple(tuple);
 	table_close(conDesc, RowExclusiveLock);
 }
@@ -1072,15 +1064,17 @@ AlterConstraintNamespaces(Oid ownerId, Oid oldNspId,
 		/* Don't update if the object is already part of the namespace */
 		if (conform->connamespace == oldNspId && oldNspId != newNspId)
 		{
+			Bitmapset  *updated = NULL;
+
 			tup = heap_copytuple(tup);
 			conform = (Form_pg_constraint) GETSTRUCT(tup);
 
-			conform->connamespace = newNspId;
-
-			CatalogTupleUpdate(conRel, &tup->t_self, tup);
+			HeapTupleUpdateField(pg_constraint, connamespace, newNspId, conform, updated);
+			CatalogTupleUpdate(conRel, &tup->t_self, tup, updated, NULL);
+			bms_free(updated);
 
 			/*
-			 * Note: currently, the constraint will not have its own
+			 * NOTE: currently, the constraint will not have its own
 			 * dependency on the namespace, so we don't need to do
 			 * changeDependencyFor().
 			 */
@@ -1116,6 +1110,7 @@ ConstraintSetParentConstraint(Oid childConstrId,
 				newtup;
 	ObjectAddress depender;
 	ObjectAddress referenced;
+	Bitmapset  *updated = NULL;
 
 	constrRel = table_open(ConstraintRelationId, RowExclusiveLock);
 	tuple = SearchSysCache1(CONSTROID, ObjectIdGetDatum(childConstrId));
@@ -1131,16 +1126,17 @@ ConstraintSetParentConstraint(Oid childConstrId,
 			elog(ERROR, "constraint %u already has a parent constraint",
 				 childConstrId);
 
-		constrForm->conislocal = false;
+		HeapTupleUpdateField(pg_constraint, conislocal, false, constrForm, updated);
 		if (pg_add_s16_overflow(constrForm->coninhcount, 1,
 								&constrForm->coninhcount))
 			ereport(ERROR,
 					errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
 					errmsg("too many inheritance parents"));
 
-		constrForm->conparentid = parentConstrId;
+		HeapTupleMarkColumnUpdated(pg_constraint, coninhcount, updated);
+		HeapTupleUpdateField(pg_constraint, conparentid, parentConstrId, constrForm, updated);
 
-		CatalogTupleUpdate(constrRel, &tuple->t_self, newtup);
+		CatalogTupleUpdate(constrRel, &tuple->t_self, newtup, updated, NULL);
 
 		ObjectAddressSet(depender, ConstraintRelationId, childConstrId);
 
@@ -1152,14 +1148,14 @@ ConstraintSetParentConstraint(Oid childConstrId,
 	}
 	else
 	{
-		constrForm->coninhcount--;
-		constrForm->conislocal = true;
-		constrForm->conparentid = InvalidOid;
+		HeapTupleUpdateField(pg_constraint, coninhcount, constrForm->coninhcount - 1, constrForm, updated);
+		HeapTupleUpdateField(pg_constraint, conislocal, true, constrForm, updated);
+		HeapTupleUpdateField(pg_constraint, conparentid, InvalidOid, constrForm, updated);
 
 		/* Make sure there's no further inheritance. */
 		Assert(constrForm->coninhcount == 0);
 
-		CatalogTupleUpdate(constrRel, &tuple->t_self, newtup);
+		CatalogTupleUpdate(constrRel, &tuple->t_self, newtup, updated, NULL);
 
 		deleteDependencyRecordsForClass(ConstraintRelationId, childConstrId,
 										ConstraintRelationId,
@@ -1169,6 +1165,7 @@ ConstraintSetParentConstraint(Oid childConstrId,
 										DEPENDENCY_PARTITION_SEC);
 	}
 
+	bms_free(updated);
 	ReleaseSysCache(tuple);
 	table_close(constrRel, RowExclusiveLock);
 }
diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c
index cb75e11fced..b132301e683 100644
--- a/src/backend/commands/alter.c
+++ b/src/backend/commands/alter.c
@@ -180,7 +180,7 @@ AlterObjectRename_internal(Relation rel, Oid objectId, const char *new_name)
 	AclResult	aclresult;
 	Datum	   *values;
 	bool	   *nulls;
-	bool	   *replaces;
+	Bitmapset  *updated = NULL;
 	NameData	nameattrdata;
 
 	oldtup = SearchSysCache1(oidCacheId, ObjectIdGetDatum(objectId));
@@ -326,15 +326,21 @@ AlterObjectRename_internal(Relation rel, Oid objectId, const char *new_name)
 	/* Build modified tuple */
 	values = palloc0(RelationGetNumberOfAttributes(rel) * sizeof(Datum));
 	nulls = palloc0(RelationGetNumberOfAttributes(rel) * sizeof(bool));
-	replaces = palloc0(RelationGetNumberOfAttributes(rel) * sizeof(bool));
+
+	/*
+	 * NOTE: We can't use the CatalogTuple*() macros here because 'Anum_name'
+	 * isn't a table/field name, it's a index for the relation passed into the
+	 * function as an argument.
+	 */
 	namestrcpy(&nameattrdata, new_name);
 	values[Anum_name - 1] = NameGetDatum(&nameattrdata);
-	replaces[Anum_name - 1] = true;
-	newtup = heap_modify_tuple(oldtup, RelationGetDescr(rel),
-							   values, nulls, replaces);
+	updated = bms_add_member(updated, Anum_name - FirstLowInvalidHeapAttributeNumber);
+
+	newtup = heap_update_tuple(oldtup, RelationGetDescr(rel),
+							   values, nulls, updated);
 
 	/* Perform actual update */
-	CatalogTupleUpdate(rel, &oldtup->t_self, newtup);
+	CatalogTupleUpdate(rel, &oldtup->t_self, newtup, updated, NULL);
 
 	InvokeObjectPostAlterHook(classId, objectId, 0);
 
@@ -357,7 +363,7 @@ AlterObjectRename_internal(Relation rel, Oid objectId, const char *new_name)
 	/* Release memory */
 	pfree(values);
 	pfree(nulls);
-	pfree(replaces);
+	bms_free(updated);
 	heap_freetuple(newtup);
 
 	ReleaseSysCache(oldtup);
@@ -705,7 +711,7 @@ AlterObjectNamespace_internal(Relation rel, Oid objid, Oid nspOid)
 				newtup;
 	Datum	   *values;
 	bool	   *nulls;
-	bool	   *replaces;
+	Bitmapset  *updated = NULL;
 
 	tup = SearchSysCacheCopy1(oidCacheId, ObjectIdGetDatum(objid));
 	if (!HeapTupleIsValid(tup)) /* should not happen */
@@ -804,19 +810,21 @@ AlterObjectNamespace_internal(Relation rel, Oid objid, Oid nspOid)
 	/* Build modified tuple */
 	values = palloc0(RelationGetNumberOfAttributes(rel) * sizeof(Datum));
 	nulls = palloc0(RelationGetNumberOfAttributes(rel) * sizeof(bool));
-	replaces = palloc0(RelationGetNumberOfAttributes(rel) * sizeof(bool));
+
+	/* NOTE: Don't use the CatalogTuple*() macros here either. */
 	values[Anum_namespace - 1] = ObjectIdGetDatum(nspOid);
-	replaces[Anum_namespace - 1] = true;
-	newtup = heap_modify_tuple(tup, RelationGetDescr(rel),
-							   values, nulls, replaces);
+	updated = bms_add_member(updated, Anum_namespace - FirstLowInvalidHeapAttributeNumber);
+
+	newtup = heap_update_tuple(tup, RelationGetDescr(rel),
+							   values, nulls, updated);
 
 	/* Perform actual update */
-	CatalogTupleUpdate(rel, &tup->t_self, newtup);
+	CatalogTupleUpdate(rel, &tup->t_self, newtup, updated, NULL);
 
 	/* Release memory */
 	pfree(values);
 	pfree(nulls);
-	pfree(replaces);
+	bms_free(updated);
 
 	/* update dependency to point to the new schema */
 	if (changeDependencyFor(classId, objid,
@@ -967,7 +975,7 @@ AlterObjectOwner_internal(Oid classId, Oid objectId, Oid new_ownerId)
 		HeapTuple	newtup;
 		Datum	   *values;
 		bool	   *nulls;
-		bool	   *replaces;
+		Bitmapset  *updated = NULL;
 
 		/* Superusers can bypass permission checks */
 		if (!superuser())
@@ -1014,9 +1022,10 @@ AlterObjectOwner_internal(Oid classId, Oid objectId, Oid new_ownerId)
 		nattrs = RelationGetNumberOfAttributes(rel);
 		values = palloc0(nattrs * sizeof(Datum));
 		nulls = palloc0(nattrs * sizeof(bool));
-		replaces = palloc0(nattrs * sizeof(bool));
+
+		/* NOTE: Don't use the CatalogTuple() macros here either */
 		values[Anum_owner - 1] = ObjectIdGetDatum(new_ownerId);
-		replaces[Anum_owner - 1] = true;
+		updated = bms_add_member(updated, Anum_owner - FirstLowInvalidHeapAttributeNumber);
 
 		/*
 		 * Determine the modified ACL for the new owner.  This is only
@@ -1032,16 +1041,18 @@ AlterObjectOwner_internal(Oid classId, Oid objectId, Oid new_ownerId)
 
 				newAcl = aclnewowner(DatumGetAclP(datum),
 									 old_ownerId, new_ownerId);
+
+				/* NOTE: Don't use the CatalogTuple*() macros here either */
 				values[Anum_acl - 1] = PointerGetDatum(newAcl);
-				replaces[Anum_acl - 1] = true;
+				updated = bms_add_member(updated, Anum_acl - FirstLowInvalidHeapAttributeNumber);
 			}
 		}
 
-		newtup = heap_modify_tuple(oldtup, RelationGetDescr(rel),
-								   values, nulls, replaces);
+		newtup = heap_update_tuple(oldtup, RelationGetDescr(rel),
+								   values, nulls, updated);
 
 		/* Perform actual update */
-		CatalogTupleUpdate(rel, &newtup->t_self, newtup);
+		CatalogTupleUpdate(rel, &newtup->t_self, newtup, updated, NULL);
 
 		UnlockTuple(rel, &oldtup->t_self, InplaceUpdateTupleLock);
 
@@ -1051,7 +1062,7 @@ AlterObjectOwner_internal(Oid classId, Oid objectId, Oid new_ownerId)
 		/* Release memory */
 		pfree(values);
 		pfree(nulls);
-		pfree(replaces);
+		bms_free(updated);
 	}
 	else
 		UnlockTuple(rel, &oldtup->t_self, InplaceUpdateTupleLock);
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 25089fae3e0..6fb774133d9 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -29,6 +29,7 @@
 #include "catalog/index.h"
 #include "catalog/indexing.h"
 #include "catalog/pg_inherits.h"
+#include "catalog/pg_statistic.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
@@ -1666,50 +1667,34 @@ update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
 	for (attno = 0; attno < natts; attno++)
 	{
 		VacAttrStats *stats = vacattrstats[attno];
-		HeapTuple	stup,
-					oldtup;
-		int			i,
-					k,
+		HeapTuple	oldtup;
+		int			k,
 					n;
-		Datum		values[Natts_pg_statistic];
-		bool		nulls[Natts_pg_statistic];
-		bool		replaces[Natts_pg_statistic];
+
+		CatalogUpdateValuesContext(pg_statistic, ctx);
 
 		/* Ignore attr if we weren't able to collect stats */
 		if (!stats->stats_valid)
 			continue;
 
-		/*
-		 * Construct a new pg_statistic tuple
-		 */
-		for (i = 0; i < Natts_pg_statistic; ++i)
-		{
-			nulls[i] = false;
-			replaces[i] = true;
-		}
+		/* Construct a new pg_statistic tuple */
+		CatalogTupleUpdateMarkAllColumnsUpdated(ctx, pg_statistic);
+		CatalogTupleSetValue(ctx, pg_statistic, starelid, ObjectIdGetDatum(relid));
+		CatalogTupleSetValue(ctx, pg_statistic, staattnum, Int16GetDatum(stats->tupattnum));
+		CatalogTupleSetValue(ctx, pg_statistic, stainherit, BoolGetDatum(inh));
+		CatalogTupleSetValue(ctx, pg_statistic, stanullfrac, Float4GetDatum(stats->stanullfrac));
+		CatalogTupleSetValue(ctx, pg_statistic, stawidth, Int32GetDatum(stats->stawidth));
+		CatalogTupleSetValue(ctx, pg_statistic, stadistinct, Float4GetDatum(stats->stadistinct));
 
-		values[Anum_pg_statistic_starelid - 1] = ObjectIdGetDatum(relid);
-		values[Anum_pg_statistic_staattnum - 1] = Int16GetDatum(stats->tupattnum);
-		values[Anum_pg_statistic_stainherit - 1] = BoolGetDatum(inh);
-		values[Anum_pg_statistic_stanullfrac - 1] = Float4GetDatum(stats->stanullfrac);
-		values[Anum_pg_statistic_stawidth - 1] = Int32GetDatum(stats->stawidth);
-		values[Anum_pg_statistic_stadistinct - 1] = Float4GetDatum(stats->stadistinct);
-		i = Anum_pg_statistic_stakind1 - 1;
 		for (k = 0; k < STATISTIC_NUM_SLOTS; k++)
-		{
-			values[i++] = Int16GetDatum(stats->stakind[k]); /* stakindN */
-		}
-		i = Anum_pg_statistic_staop1 - 1;
+			CatalogTupleSetValue(ctx, pg_statistic, stakind1 + k, Int16GetDatum(stats->stakind[k]));
+
 		for (k = 0; k < STATISTIC_NUM_SLOTS; k++)
-		{
-			values[i++] = ObjectIdGetDatum(stats->staop[k]);	/* staopN */
-		}
-		i = Anum_pg_statistic_stacoll1 - 1;
+			CatalogTupleSetValue(ctx, pg_statistic, staop1 + k, ObjectIdGetDatum(stats->staop[k]));
+
 		for (k = 0; k < STATISTIC_NUM_SLOTS; k++)
-		{
-			values[i++] = ObjectIdGetDatum(stats->stacoll[k]);	/* stacollN */
-		}
-		i = Anum_pg_statistic_stanumbers1 - 1;
+			CatalogTupleSetValue(ctx, pg_statistic, stacoll1 + k, ObjectIdGetDatum(stats->stacoll[k]));
+
 		for (k = 0; k < STATISTIC_NUM_SLOTS; k++)
 		{
 			if (stats->stanumbers[k] != NULL)
@@ -1721,15 +1706,12 @@ update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
 				for (n = 0; n < nnum; n++)
 					numdatums[n] = Float4GetDatum(stats->stanumbers[k][n]);
 				arry = construct_array_builtin(numdatums, nnum, FLOAT4OID);
-				values[i++] = PointerGetDatum(arry);	/* stanumbersN */
+				CatalogTupleSetValue(ctx, pg_statistic, stanumbers1 + k, PointerGetDatum(arry));
 			}
 			else
-			{
-				nulls[i] = true;
-				values[i++] = (Datum) 0;
-			}
+				CatalogTupleSetValueNull(ctx, pg_statistic, stanumbers1 + k);
 		}
-		i = Anum_pg_statistic_stavalues1 - 1;
+
 		for (k = 0; k < STATISTIC_NUM_SLOTS; k++)
 		{
 			if (stats->stavalues[k] != NULL)
@@ -1742,12 +1724,11 @@ update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
 									   stats->statyplen[k],
 									   stats->statypbyval[k],
 									   stats->statypalign[k]);
-				values[i++] = PointerGetDatum(arry);	/* stavaluesN */
+				CatalogTupleSetValue(ctx, pg_statistic, stavalues1 + k, PointerGetDatum(arry));
 			}
 			else
 			{
-				nulls[i] = true;
-				values[i++] = (Datum) 0;
+				CatalogTupleSetValueNull(ctx, pg_statistic, stavalues1 + k);
 			}
 		}
 
@@ -1761,29 +1742,26 @@ update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
 		if (indstate == NULL)
 			indstate = CatalogOpenIndexes(sd);
 
+		ctx->idx = indstate;
+
 		if (HeapTupleIsValid(oldtup))
 		{
-			/* Yes, replace it */
-			stup = heap_modify_tuple(oldtup,
-									 RelationGetDescr(sd),
-									 values,
-									 nulls,
-									 replaces);
+			/* Yes, replace existing tuple */
+			ModifyCatalogTupleValues(sd, oldtup, ctx);
 			ReleaseSysCache(oldtup);
-			CatalogTupleUpdateWithInfo(sd, &stup->t_self, stup, indstate);
 		}
 		else
 		{
 			/* No, insert new tuple */
-			stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
-			CatalogTupleInsertWithInfo(sd, stup, indstate);
+			InsertCatalogTupleValues(sd, ctx);
 		}
 
-		heap_freetuple(stup);
+		CatalogTupleReuseUpdateContext(ctx);
 	}
 
 	if (indstate != NULL)
 		CatalogCloseIndexes(indstate);
+
 	table_close(sd, RowExclusiveLock);
 }
 
diff --git a/src/backend/statistics/attribute_stats.c b/src/backend/statistics/attribute_stats.c
index ef4d768feab..fd4a25326b3 100644
--- a/src/backend/statistics/attribute_stats.c
+++ b/src/backend/statistics/attribute_stats.c
@@ -18,6 +18,7 @@
 #include "postgres.h"
 
 #include "access/heapam.h"
+#include "access/htup.h"
 #include "catalog/indexing.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_collation.h"
@@ -120,15 +121,13 @@ static bool get_elem_stat_type(Oid atttypid, char atttyptype,
 							   Oid *elemtypid, Oid *elem_eq_opr);
 static Datum text_to_stavalues(const char *staname, FmgrInfo *array_in, Datum d,
 							   Oid typid, int32 typmod, bool *ok);
-static void set_stats_slot(Datum *values, bool *nulls, bool *replaces,
+static void set_stats_slot(void *_ctx,
 						   int16 stakind, Oid staop, Oid stacoll,
 						   Datum stanumbers, bool stanumbers_isnull,
 						   Datum stavalues, bool stavalues_isnull);
-static void upsert_pg_statistic(Relation starel, HeapTuple oldtup,
-								const Datum *values, const bool *nulls, const bool *replaces);
+static void upsert_pg_statistic(Relation starel, HeapTuple oldtup, void *_ctx);
 static bool delete_pg_statistic(Oid reloid, AttrNumber attnum, bool stainherit);
-static void init_empty_stats_tuple(Oid reloid, int16 attnum, bool inherited,
-								   Datum *values, bool *nulls, bool *replaces);
+static void init_empty_stats_tuple(Oid reloid, int16 attnum, bool inherited, void *_ctx);
 
 /*
  * Insert or Update Attribute Statistics
@@ -182,13 +181,10 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
 	bool		do_bounds_histogram = !PG_ARGISNULL(RANGE_BOUNDS_HISTOGRAM_ARG);
 	bool		do_range_length_histogram = !PG_ARGISNULL(RANGE_LENGTH_HISTOGRAM_ARG) &&
 		!PG_ARGISNULL(RANGE_EMPTY_FRAC_ARG);
-
-	Datum		values[Natts_pg_statistic] = {0};
-	bool		nulls[Natts_pg_statistic] = {0};
-	bool		replaces[Natts_pg_statistic] = {0};
-
 	bool		result = true;
 
+	CatalogUpdateValuesContext(pg_statistic, ctx);
+
 	stats_check_required_arg(fcinfo, attarginfo, ATTRELSCHEMA_ARG);
 	stats_check_required_arg(fcinfo, attarginfo, ATTRELNAME_ARG);
 
@@ -359,27 +355,19 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
 
 	/* initialize from existing tuple if exists */
 	if (HeapTupleIsValid(statup))
-		heap_deform_tuple(statup, RelationGetDescr(starel), values, nulls);
+		DeformCatalogTuple(starel, statup, ctx);
 	else
-		init_empty_stats_tuple(reloid, attnum, inherited, values, nulls,
-							   replaces);
+		init_empty_stats_tuple(reloid, attnum, inherited, (void *) ctx);
 
 	/* if specified, set to argument values */
 	if (!PG_ARGISNULL(NULL_FRAC_ARG))
-	{
-		values[Anum_pg_statistic_stanullfrac - 1] = PG_GETARG_DATUM(NULL_FRAC_ARG);
-		replaces[Anum_pg_statistic_stanullfrac - 1] = true;
-	}
+		CatalogTupleUpdateValue(ctx, pg_statistic, stanullfrac, PG_GETARG_DATUM(NULL_FRAC_ARG));
+
 	if (!PG_ARGISNULL(AVG_WIDTH_ARG))
-	{
-		values[Anum_pg_statistic_stawidth - 1] = PG_GETARG_DATUM(AVG_WIDTH_ARG);
-		replaces[Anum_pg_statistic_stawidth - 1] = true;
-	}
+		CatalogTupleUpdateValue(ctx, pg_statistic, stawidth, PG_GETARG_DATUM(AVG_WIDTH_ARG));
+
 	if (!PG_ARGISNULL(N_DISTINCT_ARG))
-	{
-		values[Anum_pg_statistic_stadistinct - 1] = PG_GETARG_DATUM(N_DISTINCT_ARG);
-		replaces[Anum_pg_statistic_stadistinct - 1] = true;
-	}
+		CatalogTupleUpdateValue(ctx, pg_statistic, stadistinct, PG_GETARG_DATUM(N_DISTINCT_ARG));
 
 	/* STATISTIC_KIND_MCV */
 	if (do_mcv)
@@ -394,7 +382,7 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
 
 		if (converted)
 		{
-			set_stats_slot(values, nulls, replaces,
+			set_stats_slot(ctx,
 						   STATISTIC_KIND_MCV,
 						   eq_opr, atttypcoll,
 						   stanumbers, false, stavalues, false);
@@ -417,7 +405,7 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
 
 		if (converted)
 		{
-			set_stats_slot(values, nulls, replaces,
+			set_stats_slot(ctx,
 						   STATISTIC_KIND_HISTOGRAM,
 						   lt_opr, atttypcoll,
 						   0, true, stavalues, false);
@@ -433,7 +421,7 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
 		ArrayType  *arry = construct_array_builtin(elems, 1, FLOAT4OID);
 		Datum		stanumbers = PointerGetDatum(arry);
 
-		set_stats_slot(values, nulls, replaces,
+		set_stats_slot(ctx,
 					   STATISTIC_KIND_CORRELATION,
 					   lt_opr, atttypcoll,
 					   stanumbers, false, 0, true);
@@ -454,7 +442,7 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
 
 		if (converted)
 		{
-			set_stats_slot(values, nulls, replaces,
+			set_stats_slot(ctx,
 						   STATISTIC_KIND_MCELEM,
 						   elem_eq_opr, atttypcoll,
 						   stanumbers, false, stavalues, false);
@@ -468,7 +456,7 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
 	{
 		Datum		stanumbers = PG_GETARG_DATUM(ELEM_COUNT_HISTOGRAM_ARG);
 
-		set_stats_slot(values, nulls, replaces,
+		set_stats_slot(ctx,
 					   STATISTIC_KIND_DECHIST,
 					   elem_eq_opr, atttypcoll,
 					   stanumbers, false, 0, true);
@@ -494,7 +482,7 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
 
 		if (converted)
 		{
-			set_stats_slot(values, nulls, replaces,
+			set_stats_slot(ctx,
 						   STATISTIC_KIND_BOUNDS_HISTOGRAM,
 						   InvalidOid, InvalidOid,
 						   0, true, stavalues, false);
@@ -521,7 +509,7 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
 
 		if (converted)
 		{
-			set_stats_slot(values, nulls, replaces,
+			set_stats_slot(ctx,
 						   STATISTIC_KIND_RANGE_LENGTH_HISTOGRAM,
 						   Float8LessOperator, InvalidOid,
 						   stanumbers, false, stavalues, false);
@@ -530,7 +518,7 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
 			result = false;
 	}
 
-	upsert_pg_statistic(starel, statup, values, nulls, replaces);
+	upsert_pg_statistic(starel, statup, ctx);
 
 	if (HeapTupleIsValid(statup))
 		ReleaseSysCache(statup);
@@ -760,92 +748,75 @@ text_to_stavalues(const char *staname, FmgrInfo *array_in, Datum d, Oid typid,
  * slot.
  */
 static void
-set_stats_slot(Datum *values, bool *nulls, bool *replaces,
+set_stats_slot(void *_ctx,
 			   int16 stakind, Oid staop, Oid stacoll,
 			   Datum stanumbers, bool stanumbers_isnull,
 			   Datum stavalues, bool stavalues_isnull)
 {
-	int			slotidx;
+	CatalogUpdateValuesDecl(pg_statistic, ctx) * ctx = _ctx;
+	int			i = 0;
 	int			first_empty = -1;
-	AttrNumber	stakind_attnum;
-	AttrNumber	staop_attnum;
-	AttrNumber	stacoll_attnum;
 
-	/* find existing slot with given stakind */
-	for (slotidx = 0; slotidx < STATISTIC_NUM_SLOTS; slotidx++)
+	/*
+	 * This might seem odd, to be adding 'i' to the name of the field on these
+	 * macros, but that's what we need to do here to find the proper slot
+	 * offset and record the proper value into the updated bitmap.
+	 *
+	 * Example: CatalogTupleValue(ctx, pg_statistic, stakind1 + i);
+	 *
+	 * Becomes: ctx.values[Anum_pg_statistic_stakind1 + i - 1];
+	 *
+	 * The result is you're indexing the i'th value, exactly what we needed.
+	 */
+
+	/* Find existing slot with given stakind */
+	for (i = 0; i < STATISTIC_NUM_SLOTS; i++)
 	{
-		stakind_attnum = Anum_pg_statistic_stakind1 - 1 + slotidx;
+		Datum		d = CatalogTupleValue(ctx, pg_statistic, stakind1 + i);
+		int16		v = DatumGetInt16(d);
+
+		if (first_empty < 0 && v == 0)
+			first_empty = i;
 
-		if (first_empty < 0 &&
-			DatumGetInt16(values[stakind_attnum]) == 0)
-			first_empty = slotidx;
-		if (DatumGetInt16(values[stakind_attnum]) == stakind)
+		if (v == stakind)
 			break;
 	}
 
-	if (slotidx >= STATISTIC_NUM_SLOTS && first_empty >= 0)
-		slotidx = first_empty;
+	if (i >= STATISTIC_NUM_SLOTS && first_empty >= 0)
+		i = first_empty;
 
-	if (slotidx >= STATISTIC_NUM_SLOTS)
+	if (i >= STATISTIC_NUM_SLOTS)
 		ereport(ERROR,
-				(errmsg("maximum number of statistics slots exceeded: %d",
-						slotidx + 1)));
+				(errmsg("maximum number of statistics slots exceeded: %d", i + 1)));
 
-	stakind_attnum = Anum_pg_statistic_stakind1 - 1 + slotidx;
-	staop_attnum = Anum_pg_statistic_staop1 - 1 + slotidx;
-	stacoll_attnum = Anum_pg_statistic_stacoll1 - 1 + slotidx;
+	if (DatumGetInt16(CatalogTupleValue(ctx, pg_statistic, stakind1 + i)) != stakind)
+		CatalogTupleUpdateValue(ctx, pg_statistic, stakind1 + i, Int16GetDatum(stakind));
+
+	if (DatumGetInt16(CatalogTupleValue(ctx, pg_statistic, staop1 + i)) != staop)
+		CatalogTupleUpdateValue(ctx, pg_statistic, staop1 + i, ObjectIdGetDatum(staop));
+
+	if (DatumGetInt16(CatalogTupleValue(ctx, pg_statistic, stacoll1 + i)) != stacoll)
+		CatalogTupleUpdateValue(ctx, pg_statistic, stacoll1 + i, ObjectIdGetDatum(stacoll));
 
-	if (DatumGetInt16(values[stakind_attnum]) != stakind)
-	{
-		values[stakind_attnum] = Int16GetDatum(stakind);
-		replaces[stakind_attnum] = true;
-	}
-	if (DatumGetObjectId(values[staop_attnum]) != staop)
-	{
-		values[staop_attnum] = ObjectIdGetDatum(staop);
-		replaces[staop_attnum] = true;
-	}
-	if (DatumGetObjectId(values[stacoll_attnum]) != stacoll)
-	{
-		values[stacoll_attnum] = ObjectIdGetDatum(stacoll);
-		replaces[stacoll_attnum] = true;
-	}
 	if (!stanumbers_isnull)
-	{
-		values[Anum_pg_statistic_stanumbers1 - 1 + slotidx] = stanumbers;
-		nulls[Anum_pg_statistic_stanumbers1 - 1 + slotidx] = false;
-		replaces[Anum_pg_statistic_stanumbers1 - 1 + slotidx] = true;
-	}
+		CatalogTupleUpdateValue(ctx, pg_statistic, stanumbers1 + i, stanumbers);
+
 	if (!stavalues_isnull)
-	{
-		values[Anum_pg_statistic_stavalues1 - 1 + slotidx] = stavalues;
-		nulls[Anum_pg_statistic_stavalues1 - 1 + slotidx] = false;
-		replaces[Anum_pg_statistic_stavalues1 - 1 + slotidx] = true;
-	}
+		CatalogTupleUpdateValue(ctx, pg_statistic, stavalues1 + i, stavalues);
 }
 
 /*
  * Upsert the pg_statistic record.
  */
 static void
-upsert_pg_statistic(Relation starel, HeapTuple oldtup,
-					const Datum *values, const bool *nulls, const bool *replaces)
+upsert_pg_statistic(Relation starel, HeapTuple oldtup, void *_ctx)
 {
-	HeapTuple	newtup;
+	CatalogUpdateValuesDecl(pg_statistic, ctx) * ctx = _ctx;
 
 	if (HeapTupleIsValid(oldtup))
-	{
-		newtup = heap_modify_tuple(oldtup, RelationGetDescr(starel),
-								   values, nulls, replaces);
-		CatalogTupleUpdate(starel, &newtup->t_self, newtup);
-	}
+		ModifyCatalogTupleValues(starel, oldtup, ctx);
 	else
-	{
-		newtup = heap_form_tuple(RelationGetDescr(starel), values, nulls);
-		CatalogTupleInsert(starel, newtup);
-	}
-
-	heap_freetuple(newtup);
+		InsertCatalogTupleValues(starel, ctx);
 
 	CommandCounterIncrement();
 }
@@ -884,37 +855,28 @@ delete_pg_statistic(Oid reloid, AttrNumber attnum, bool stainherit)
  * Initialize values and nulls for a new stats tuple.
  */
 static void
-init_empty_stats_tuple(Oid reloid, int16 attnum, bool inherited,
-					   Datum *values, bool *nulls, bool *replaces)
+init_empty_stats_tuple(Oid reloid, int16 attnum, bool inherited, void *_ctx)
 {
-	memset(nulls, true, sizeof(bool) * Natts_pg_statistic);
-	memset(replaces, true, sizeof(bool) * Natts_pg_statistic);
+	CatalogUpdateValuesDecl(pg_statistic, ctx) * ctx = (struct _cat_pg_statistic_ctx_upd_vals *) _ctx;
 
-	/* must initialize non-NULL attributes */
+	CatalogTupleUpdateMarkAllColumnsUpdated(ctx, pg_statistic);
+	memset(ctx->nulls, true, sizeof(bool) * Natts_pg_statistic);
 
-	values[Anum_pg_statistic_starelid - 1] = ObjectIdGetDatum(reloid);
-	nulls[Anum_pg_statistic_starelid - 1] = false;
-	values[Anum_pg_statistic_staattnum - 1] = Int16GetDatum(attnum);
-	nulls[Anum_pg_statistic_staattnum - 1] = false;
-	values[Anum_pg_statistic_stainherit - 1] = BoolGetDatum(inherited);
-	nulls[Anum_pg_statistic_stainherit - 1] = false;
+	/* Now we must initialize non-NULL attributes */
+	CatalogTupleUpdateValue(ctx, pg_statistic, starelid, ObjectIdGetDatum(reloid));
+	CatalogTupleUpdateValue(ctx, pg_statistic, staattnum, Int16GetDatum(attnum));
+	CatalogTupleUpdateValue(ctx, pg_statistic, stainherit, BoolGetDatum(inherited));
 
-	values[Anum_pg_statistic_stanullfrac - 1] = DEFAULT_NULL_FRAC;
-	nulls[Anum_pg_statistic_stanullfrac - 1] = false;
-	values[Anum_pg_statistic_stawidth - 1] = DEFAULT_AVG_WIDTH;
-	nulls[Anum_pg_statistic_stawidth - 1] = false;
-	values[Anum_pg_statistic_stadistinct - 1] = DEFAULT_N_DISTINCT;
-	nulls[Anum_pg_statistic_stadistinct - 1] = false;
+	CatalogTupleUpdateValue(ctx, pg_statistic, stanullfrac, DEFAULT_NULL_FRAC);
+	CatalogTupleUpdateValue(ctx, pg_statistic, stawidth, DEFAULT_AVG_WIDTH);
+	CatalogTupleUpdateValue(ctx, pg_statistic, stadistinct, DEFAULT_N_DISTINCT);
 
 	/* initialize stakind, staop, and stacoll slots */
-	for (int slotnum = 0; slotnum < STATISTIC_NUM_SLOTS; slotnum++)
+	for (int i = 0; i < STATISTIC_NUM_SLOTS; i++)
 	{
-		values[Anum_pg_statistic_stakind1 + slotnum - 1] = (Datum) 0;
-		nulls[Anum_pg_statistic_stakind1 + slotnum - 1] = false;
-		values[Anum_pg_statistic_staop1 + slotnum - 1] = ObjectIdGetDatum(InvalidOid);
-		nulls[Anum_pg_statistic_staop1 + slotnum - 1] = false;
-		values[Anum_pg_statistic_stacoll1 + slotnum - 1] = ObjectIdGetDatum(InvalidOid);
-		nulls[Anum_pg_statistic_stacoll1 + slotnum - 1] = false;
+		CatalogTupleUpdateValue(ctx, pg_statistic, stakind1 + i, (Datum) 0);
+		CatalogTupleUpdateValue(ctx, pg_statistic, staop1 + i, ObjectIdGetDatum(InvalidOid));
+		CatalogTupleUpdateValue(ctx, pg_statistic, stacoll1 + i, ObjectIdGetDatum(InvalidOid));
 	}
 }
 
diff --git a/src/include/access/htup.h b/src/include/access/htup.h
index f6b766697e2..896a1e804ca 100644
--- a/src/include/access/htup.h
+++ b/src/include/access/htup.h
@@ -14,6 +14,8 @@
 #ifndef HTUP_H
 #define HTUP_H
 
+#include "catalog/pg_aggregate_d.h"
+#include "nodes/bitmapset.h"
 #include "storage/itemptr.h"
 
 /* typedefs and forward declarations for structs defined in htup_details.h */
@@ -77,6 +79,254 @@ typedef HeapTupleData *HeapTuple;
  */
 #define HeapTupleIsValid(tuple) ((tuple) != NULL)
 
+#define HeapTupleSetValue(table_name, field, value, values) \
+	(values)[Anum_##table_name##_##field - 1] = (value)
+
+#define HeapTupleSetValueNull(table_name, field, values, nulls) \
+	do { \
+		(values)[Anum_##table_name##_##field - 1] = (Datum) 0; \
+		(nulls)[Anum_##table_name##_##field - 1] = true; \
+	} while(0)
+
+/*
+ * The following should be used when manipulating CatalogTuples for
+ * insert or update.
+ */
+
+#define CatalogInsertValuesDecl(table_name, var) \
+	struct _cat_##table_name##_##var##_ins_vals { \
+		CatalogIndexState	idx; \
+		bool	nulls[Natts_##table_name]; \
+		Datum	values[Natts_##table_name]; \
+	}
+#define CatalogInsertValuesContext(table_name, var) \
+	CatalogInsertValuesDecl(table_name, var) _##table_name##_##var = { \
+		.idx	= NULL, \
+		.nulls	= {false}, \
+		.values	= {0} \
+	}, *var = &_##table_name##_##var
+
+#define CatalogUpdateValuesDecl(table_name, var) \
+	struct _cat_##table_name##_##var##_upd_vals { \
+		CatalogIndexState	idx; \
+		bool		nulls[Natts_##table_name]; \
+		Datum		values[Natts_##table_name]; \
+		Bitmapset	*updated; \
+	}
+#define CatalogUpdateValuesContext(table_name, var) \
+	CatalogUpdateValuesDecl(table_name, var) _##table_name##_##var = { \
+		.idx		= NULL, \
+		.nulls		= {false}, \
+		.values		= {0}, \
+		.updated	= NULL \
+	}, *var = &_##table_name##_##var
+
+#define CatalogFormContext(table_name, var, tuple) \
+	Form_##table_name var = (Form_##table_name) GETSTRUCT(tuple)
+
+#define CatalogInsertFormDecl(table_name, var) \
+	struct _cat_##table_name##_##var##_ins_form { \
+		CatalogIndexState	idx; \
+		Form_##table_name	form; \
+		bool				nulls[Natts_##table_name]; \
+	}
+#define CatalogInsertFormContext(table_name, var) \
+	CatalogInsertFormDecl(table_name, var) _##table_name##_##var = { \
+		.idx		= NULL, \
+		.form		= NULL, \
+		.nulls		= {false} \
+	}, *var = &_##table_name##_##var
+
+#define CatalogUpdateFormDecl(table_name, var) \
+	struct _cat_##table_name##_##var##_upd_form { \
+		CatalogIndexState	idx; \
+		Form_##table_name	form; \
+		bool				nulls[Natts_##table_name]; \
+		Bitmapset			*updated; \
+	}
+#define CatalogUpdateFormContext(table_name, var) \
+	CatalogUpdateFormDecl(table_name, var) _##table_name##_##var = { \
+		.idx		= NULL, \
+		.form		= NULL, \
+		.nulls		= {false}, \
+		.updated	= NULL \
+	}, *var = &_##table_name##_##var
+
+#define CatalogSetForm(table_name, var, tuple) \
+	var->form = (Form_##table_name) GETSTRUCT(tuple)
+
+#define CatalogGetFormField(var, field) var->form->field
+
+#define CatalogTupleValue(var, table_name, field) \
+	var->values[Anum_##table_name##_##field - 1]
+
+#define FormCatalogTuple(relation, var) \
+	heap_form_tuple(RelationGetDescr(relation), var->values, var->nulls)
+
+#define DeformCatalogTuple(relation, tuple, var) \
+	heap_deform_tuple(tuple, RelationGetDescr(relation), var->values, var->nulls)
+
+#define ModifyCatalogTupleValues(relation, tuple, var) \
+	do { \
+		HeapTuple new; \
+		\
+		new = heap_update_tuple(tuple, RelationGetDescr(relation), \
+								var->values, var->nulls, var->updated); \
+		CatalogTupleUpdate(relation, &new->t_self, new, var->updated, var->idx); \
+		heap_freetuple(new); \
+	} while(0)
+
+#define UpdateCatalogTupleForm(relation, tuple, var) \
+	CatalogTupleUpdate(relation, &tuple->t_self, tuple, var->updated, var->idx)
+
+#define ModifyCatalogTupleForm(relation, tuple, var) \
+	do { \
+		HeapTuple new; \
+		\
+		new = heap_update_tuple(tuple, RelationGetDescr(relation), \
+								NULL, var->nulls, var->updated); \
+		CatalogTupleUpdate(relation, &new->t_self, new, var->updated, var->idx); \
+		heap_freetuple(new); \
+	} while(0)
+
+#define InsertCatalogTupleValues(relation, var) \
+	do { \
+		HeapTuple new = heap_form_tuple(RelationGetDescr(relation), \
+								  var->values, var->nulls); \
+		CatalogTupleInsert(relation, new, var->idx); \
+		heap_freetuple(new); \
+	} while(0)
+
+#define CatalogTupleSetField(var, table_name, field, value) \
+	var->field = (value)
+
+#define CatalogTupleSetFieldNull(var, table_name, field) \
+		var->nulls[Anum_##table_name##_##field - 1] = true
+
+#define CatalogTupleSetValue(var, table_name, field, value) \
+	do { \
+		var->values[Anum_##table_name##_##field - 1] = (value); \
+		var->nulls[Anum_##table_name##_##field - 1] = false; \
+	} while(0)
+
+#define CatalogTupleSetValueNull(var, table_name, field) \
+	var->nulls[Anum_##table_name##_##field - 1] = true
+
+#define CatalogTupleUpdateValue(var, table_name, field, value) \
+	do { \
+		var->values[Anum_##table_name##_##field - 1] = (value); \
+		var->nulls[Anum_##table_name##_##field - 1] = false; \
+		var->updated = bms_add_member(var->updated, \
+								Anum_##table_name##_##field - \
+								FirstLowInvalidHeapAttributeNumber); \
+	} while(0)
+
+#define CatalogTupleUpdateValueNull(var, table_name, field) \
+	do { \
+		var->nulls[Anum_##table_name##_##field - 1] = true; \
+		var->updated = bms_add_member(var->updated, \
+								Anum_##table_name##_##field - \
+								FirstLowInvalidHeapAttributeNumber); \
+	} while(0)
+
+#define CatalogTupleUpdateField(var, table_name, field, value) \
+	do { \
+		var->form->field = (value); \
+		var->nulls[Anum_##table_name##_##field - 1] = false; \
+		var->updated = bms_add_member(var->updated, \
+								Anum_##table_name##_##field - \
+								FirstLowInvalidHeapAttributeNumber); \
+	} while(0)
+
+#define CatalogTupleCondUpdateField(var, table_name, field, cond_set_value, stmt) \
+	if (!(cond_set_value)) \
+	{ \
+		var->nulls[Anum_##table_name##_##field - 1] = false; \
+		var->updated = bms_add_member(var->updated, \
+								Anum_##table_name##_##field - \
+								FirstLowInvalidHeapAttributeNumber); \
+	} else stmt
+
+#define CatalogTupleUpdateStrField(var, table_name, field, value) \
+	do { \
+		namestrcpy(&(var->form->field), value); \
+		var->nulls[Anum_##table_name##_##field - 1] = false; \
+		var->updated = bms_add_member(var->updated, \
+								Anum_##table_name##_##field - \
+								FirstLowInvalidHeapAttributeNumber); \
+	} while(0)
+
+#define CatalogTupleUpdateFieldNull(var, table_name, field) \
+	do { \
+		var->nulls[Anum_##table_name##_##field - 1] = true; \
+		var->updated = bms_add_member(var->updated, \
+								Anum_##table_name##_##field - \
+								FirstLowInvalidHeapAttributeNumber); \
+	} while(0)
+
+#define CatalogTupleUpdateMarkAllColumnsUpdated(var, table_name) \
+	var->updated = bms_add_range(var->updated, 1 - FirstLowInvalidHeapAttributeNumber, \
+						  Natts_##table_name - FirstLowInvalidHeapAttributeNumber)
+
+#define CatalogTupleReuseUpdateContext(var) \
+	do { \
+		bms_free(var->updated); \
+		var->updated = NULL; \
+	} while(0)
+
+#define CatalogTupleHasChanged(var) (!bms_is_empty(var->updated))
+
+
+
+/* TODO BELOW */
+
+#define HeapTupleValue(table_name, field, values) \
+	(values)[Anum_##table_name##_##field - 1]
+
+#define HeapTupleSetField(table_name, field, value, form_ptr) \
+	(form_ptr)->field = (value)
+
+#define HeapTupleSetFieldNull(table_name, field, nulls) \
+	(nulls)[Anum_##table_name##_##field - 1] = true
+
+/*
+ * These are useful when forming tuples for CatalogTupleUpdate()
+ *
+ * Updated catalog tuples need to track which fields were changed when
+ * calling  heap_update_tuple(), so we use a bitmap to keep track of that.
+ */
+#define HeapTupleMarkColumnUpdated(table_name, field, updated) \
+	(updated) = bms_add_member((updated), \
+		Anum_##table_name##_##field - FirstLowInvalidHeapAttributeNumber)
+
+#define HeapTupleUpdateSetAllColumnsUpdated(table_name, updated) \
+	(updated) = bms_add_range((updated), 1 - FirstLowInvalidHeapAttributeNumber, \
+						  Natts_##table_name - FirstLowInvalidHeapAttributeNumber)
+
+#define HeapTupleSetColumnNotUpdated(table_name, field, updated) \
+	(updated) = bms_del_member((updated), \
+		Anum_##table_name##_##field - FirstLowInvalidHeapAttributeNumber)
+
+#define HeapTupleUpdateField(table_name, field, value, form_ptr, updated) \
+	do { \
+		(form_ptr)->field = (value); \
+		HeapTupleMarkColumnUpdated(table_name, field, updated); \
+	} while(0)
+
+#define HeapTupleUpdateValue(table_name, field, value, values, nulls, updated) \
+	do { \
+		(values)[Anum_##table_name##_##field - 1] = (Datum) (value); \
+		(nulls)[Anum_##table_name##_##field - 1] = false; \
+		HeapTupleMarkColumnUpdated(table_name, field, updated); \
+	} while(0)
+
+#define HeapTupleUpdateValueNull(table_name, field, values, nulls, updated) \
+	do { \
+		(values)[Anum_##table_name##_##field - 1] = (Datum) 0; \
+		(nulls)[Anum_##table_name##_##field - 1] = true; \
+		HeapTupleMarkColumnUpdated(table_name, field, updated); \
+	} while(0)
+
 /* HeapTupleHeader functions implemented in utils/time/combocid.c */
 extern CommandId HeapTupleHeaderGetCmin(const HeapTupleHeaderData *tup);
 extern CommandId HeapTupleHeaderGetCmax(const HeapTupleHeaderData *tup);
diff --git a/src/include/access/htup_details.h b/src/include/access/htup_details.h
index f3593acc8c2..814d764ee97 100644
--- a/src/include/access/htup_details.h
+++ b/src/include/access/htup_details.h
@@ -21,6 +21,9 @@
 #include "storage/bufpage.h"
 #include "varatt.h"
 
+/* Forward declarations */
+typedef struct Bitmapset Bitmapset;
+
 /*
  * MaxTupleAttributeNumber limits the number of (user) columns in a tuple.
  * The key limit on this value is that the size of the fixed overhead for
@@ -835,6 +838,11 @@ extern HeapTuple heap_modify_tuple_by_cols(HeapTuple tuple,
 										   const int *replCols,
 										   const Datum *replValues,
 										   const bool *replIsnull);
+extern HeapTuple heap_update_tuple(HeapTuple tuple,
+								   TupleDesc tupleDesc,
+								   const Datum *replValues,
+								   const bool *replIsnull,
+								   const Bitmapset *replaces);
 extern void heap_deform_tuple(HeapTuple tuple, TupleDesc tupleDesc,
 							  Datum *values, bool *isnull);
 extern void heap_freetuple(HeapTuple htup);
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index 77c17d3fb7a..685ff3d2bd1 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -37,18 +37,13 @@ typedef struct ResultRelInfo *CatalogIndexState;
  */
 extern CatalogIndexState CatalogOpenIndexes(Relation heapRel);
 extern void CatalogCloseIndexes(CatalogIndexState indstate);
-extern void CatalogTupleInsert(Relation heapRel, HeapTuple tup);
-extern void CatalogTupleInsertWithInfo(Relation heapRel, HeapTuple tup,
-									   CatalogIndexState indstate);
-extern void CatalogTuplesMultiInsertWithInfo(Relation heapRel,
-											 TupleTableSlot **slot,
-											 int ntuples,
-											 CatalogIndexState indstate);
+extern void CatalogTupleInsert(Relation heapRel, HeapTuple tup,
+							   CatalogIndexState indstate);
+extern void CatalogTuplesMultiInsert(Relation heapRel, TupleTableSlot **slot,
+									 int ntuples, CatalogIndexState indstate);
 extern void CatalogTupleUpdate(Relation heapRel, const ItemPointerData *otid,
-							   HeapTuple tup);
-extern void CatalogTupleUpdateWithInfo(Relation heapRel,
-									   const ItemPointerData *otid, HeapTuple tup,
-									   CatalogIndexState indstate);
+							   HeapTuple tuple, const struct Bitmapset *updated,
+							   CatalogIndexState indstate);
 extern void CatalogTupleDelete(Relation heapRel, const ItemPointerData *tid);
 
 #endif							/* INDEXING_H */
-- 
2.49.0

