v1-0003-Use-a-compact-struct-to-deform-tuples.patch

application/octet-stream

Filename: v1-0003-Use-a-compact-struct-to-deform-tuples.patch
Type: application/octet-stream
Part: 4
Message: Make tuple deformation faster

Patch

Same data as JSON: GET /api/v1/attachments/:id/patch the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes. API reference →
Format: format-patch
Series: patch v1-0003
Subject: Use a compact struct to deform tuples
File+
contrib/amcheck/verify_heapam.c 3 3
contrib/pageinspect/heapfuncs.c 2 2
contrib/postgres_fdw/postgres_fdw.c 6 6
src/backend/access/brin/brin_inclusion.c 9 9
src/backend/access/brin/brin_tuple.c 2 2
src/backend/access/common/attmap.c 9 5
src/backend/access/common/heaptuple.c 32 28
src/backend/access/common/indextuple.c 14 14
src/backend/access/common/tupdesc.c 52 6
src/backend/access/gin/ginbulk.c 3 3
src/backend/access/gin/ginget.c 4 4
src/backend/access/gist/gistbuild.c 4 2
src/backend/access/heap/heapam.c 5 5
src/backend/access/heap/heapam_handler.c 1 1
src/backend/access/heap/heaptoast.c 3 3
src/backend/access/nbtree/nbtutils.c 3 3
src/backend/access/spgist/spgdoinsert.c 1 1
src/backend/access/table/toast_helper.c 1 1
src/backend/catalog/index.c 2 0
src/backend/commands/copy.c 3 3
src/backend/commands/tablecmds.c 5 0
src/backend/executor/execExpr.c 4 4
src/backend/executor/execExprInterp.c 4 4
src/backend/executor/execJunk.c 1 1
src/backend/executor/execTuples.c 7 7
src/backend/executor/functions.c 2 1
src/backend/executor/nodeMemoize.c 6 6
src/backend/executor/nodeModifyTable.c 4 4
src/backend/executor/nodeValuesscan.c 2 2
src/backend/executor/tstoreReceiver.c 4 4
src/backend/optimizer/util/plancat.c 4 4
src/backend/replication/pgoutput/pgoutput.c 1 1
src/backend/utils/adt/expandedrecord.c 11 11
src/backend/utils/adt/ri_triggers.c 2 2
src/backend/utils/cache/relcache.c 12 0
src/include/access/htup_details.h 4 4
src/include/access/itup.h 7 4
src/include/access/tupdesc.h 49 3
src/include/access/tupmacs.h 2 0
From d022fc14b5c5a67dd538e2464d55c05c4a68dddd Mon Sep 17 00:00:00 2001
From: David Rowley <dgrowley@gmail.com>
Date: Tue, 28 May 2024 20:10:50 +1200
Subject: [PATCH v1 3/4] Use a compact struct to deform tuples

---
 contrib/amcheck/verify_heapam.c             |  6 +--
 contrib/pageinspect/heapfuncs.c             |  4 +-
 contrib/postgres_fdw/postgres_fdw.c         | 12 ++---
 src/backend/access/brin/brin_inclusion.c    | 18 +++----
 src/backend/access/brin/brin_tuple.c        |  4 +-
 src/backend/access/common/attmap.c          | 14 +++--
 src/backend/access/common/heaptuple.c       | 60 +++++++++++----------
 src/backend/access/common/indextuple.c      | 28 +++++-----
 src/backend/access/common/tupdesc.c         | 58 +++++++++++++++++---
 src/backend/access/gin/ginbulk.c            |  6 +--
 src/backend/access/gin/ginget.c             |  8 +--
 src/backend/access/gist/gistbuild.c         |  6 ++-
 src/backend/access/heap/heapam.c            | 10 ++--
 src/backend/access/heap/heapam_handler.c    |  2 +-
 src/backend/access/heap/heaptoast.c         |  6 +--
 src/backend/access/nbtree/nbtutils.c        |  6 +--
 src/backend/access/spgist/spgdoinsert.c     |  2 +-
 src/backend/access/table/toast_helper.c     |  2 +-
 src/backend/catalog/index.c                 |  2 +
 src/backend/commands/copy.c                 |  6 +--
 src/backend/commands/tablecmds.c            |  5 ++
 src/backend/executor/execExpr.c             |  8 +--
 src/backend/executor/execExprInterp.c       |  8 +--
 src/backend/executor/execJunk.c             |  2 +-
 src/backend/executor/execTuples.c           | 14 ++---
 src/backend/executor/functions.c            |  3 +-
 src/backend/executor/nodeMemoize.c          | 12 ++---
 src/backend/executor/nodeModifyTable.c      |  8 +--
 src/backend/executor/nodeValuesscan.c       |  4 +-
 src/backend/executor/tstoreReceiver.c       |  8 +--
 src/backend/optimizer/util/plancat.c        |  8 +--
 src/backend/replication/pgoutput/pgoutput.c |  2 +-
 src/backend/utils/adt/expandedrecord.c      | 22 ++++----
 src/backend/utils/adt/ri_triggers.c         |  4 +-
 src/backend/utils/cache/relcache.c          | 12 +++++
 src/include/access/htup_details.h           |  8 +--
 src/include/access/itup.h                   | 11 ++--
 src/include/access/tupdesc.h                | 52 ++++++++++++++++--
 src/include/access/tupmacs.h                |  2 +
 39 files changed, 290 insertions(+), 163 deletions(-)

diff --git a/contrib/amcheck/verify_heapam.c b/contrib/amcheck/verify_heapam.c
index f2526ed63a..125d594826 100644
--- a/contrib/amcheck/verify_heapam.c
+++ b/contrib/amcheck/verify_heapam.c
@@ -1567,11 +1567,11 @@ check_tuple_attribute(HeapCheckContext *ctx)
 	struct varlena *attr;
 	char	   *tp;				/* pointer to the tuple data */
 	uint16		infomask;
-	Form_pg_attribute thisatt;
+	TupleDescDeformAttr *thisatt;
 	struct varatt_external toast_pointer;
 
 	infomask = ctx->tuphdr->t_infomask;
-	thisatt = TupleDescAttr(RelationGetDescr(ctx->rel), ctx->attnum);
+	thisatt = TupleDescDeformAttr(RelationGetDescr(ctx->rel), ctx->attnum);
 
 	tp = (char *) ctx->tuphdr + ctx->tuphdr->t_hoff;
 
@@ -1612,7 +1612,7 @@ check_tuple_attribute(HeapCheckContext *ctx)
 									tp + ctx->offset);
 
 	/* Get the (possibly corrupt) varlena datum */
-	attdatum = fetchatt(thisatt, tp + ctx->offset);
+	attdatum = fetchatt_fast(thisatt, tp + ctx->offset);
 
 	/*
 	 * We have the datum, but we cannot decode it carelessly, as it may still
diff --git a/contrib/pageinspect/heapfuncs.c b/contrib/pageinspect/heapfuncs.c
index 3faeabc711..e610aa69c6 100644
--- a/contrib/pageinspect/heapfuncs.c
+++ b/contrib/pageinspect/heapfuncs.c
@@ -331,11 +331,11 @@ tuple_data_split_internal(Oid relid, char *tupdata,
 
 	for (i = 0; i < nattrs; i++)
 	{
-		Form_pg_attribute attr;
+		TupleDescDeformAttr *attr;
 		bool		is_null;
 		bytea	   *attr_data = NULL;
 
-		attr = TupleDescAttr(tupdesc, i);
+		attr = TupleDescDeformAttr(tupdesc, i);
 
 		/*
 		 * Tuple header can specify fewer attributes than tuple descriptor as
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index 0bb9a5ae8f..10906a1363 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -1804,9 +1804,9 @@ postgresPlanForeignModify(PlannerInfo *root,
 
 		for (attnum = 1; attnum <= tupdesc->natts; attnum++)
 		{
-			Form_pg_attribute attr = TupleDescAttr(tupdesc, attnum - 1);
+			TupleDescDeformAttr *attr = TupleDescDeformAttr(tupdesc, attnum - 1);
 
-			if (!attr->attisdropped)
+			if (!DeformAttrIsDropped(attr))
 				targetAttrs = lappend_int(targetAttrs, attnum);
 		}
 	}
@@ -2177,9 +2177,9 @@ postgresBeginForeignInsert(ModifyTableState *mtstate,
 	/* We transmit all columns that are defined in the foreign table. */
 	for (attnum = 1; attnum <= tupdesc->natts; attnum++)
 	{
-		Form_pg_attribute attr = TupleDescAttr(tupdesc, attnum - 1);
+		TupleDescDeformAttr *attr = TupleDescDeformAttr(tupdesc, attnum - 1);
 
-		if (!attr->attisdropped)
+		if (!DeformAttrIsDropped(attr))
 			targetAttrs = lappend_int(targetAttrs, attnum);
 	}
 
@@ -4291,12 +4291,12 @@ convert_prep_stmt_params(PgFdwModifyState *fmstate,
 			foreach(lc, fmstate->target_attrs)
 			{
 				int			attnum = lfirst_int(lc);
-				Form_pg_attribute attr = TupleDescAttr(tupdesc, attnum - 1);
+				TupleDescDeformAttr *attr = TupleDescDeformAttr(tupdesc, attnum - 1);
 				Datum		value;
 				bool		isnull;
 
 				/* Ignore generated columns; they are set to DEFAULT */
-				if (attr->attgenerated)
+				if (DeformAttrIsGenerated(attr))
 					continue;
 				value = slot_getattr(slots[i], attnum, &isnull);
 				if (isnull)
diff --git a/src/backend/access/brin/brin_inclusion.c b/src/backend/access/brin/brin_inclusion.c
index 750276998c..289418706d 100644
--- a/src/backend/access/brin/brin_inclusion.c
+++ b/src/backend/access/brin/brin_inclusion.c
@@ -146,12 +146,12 @@ brin_inclusion_add_value(PG_FUNCTION_ARGS)
 	Datum		result;
 	bool		new = false;
 	AttrNumber	attno;
-	Form_pg_attribute attr;
+	TupleDescDeformAttr *attr;
 
 	Assert(!isnull);
 
 	attno = column->bv_attno;
-	attr = TupleDescAttr(bdesc->bd_tupdesc, attno - 1);
+	attr = TupleDescDeformAttr(bdesc->bd_tupdesc, attno - 1);
 
 	/*
 	 * If the recorded value is null, copy the new value (which we know to be
@@ -160,7 +160,7 @@ brin_inclusion_add_value(PG_FUNCTION_ARGS)
 	if (column->bv_allnulls)
 	{
 		column->bv_values[INCLUSION_UNION] =
-			datumCopy(newval, attr->attbyval, attr->attlen);
+			datumCopy(newval,  DeformAttrByVal(attr), attr->attlen);
 		column->bv_values[INCLUSION_UNMERGEABLE] = BoolGetDatum(false);
 		column->bv_values[INCLUSION_CONTAINS_EMPTY] = BoolGetDatum(false);
 		column->bv_allnulls = false;
@@ -225,13 +225,13 @@ brin_inclusion_add_value(PG_FUNCTION_ARGS)
 	Assert(finfo != NULL);
 	result = FunctionCall2Coll(finfo, colloid,
 							   column->bv_values[INCLUSION_UNION], newval);
-	if (!attr->attbyval &&
+	if (!DeformAttrByVal(attr) &&
 		DatumGetPointer(result) != DatumGetPointer(column->bv_values[INCLUSION_UNION]))
 	{
 		pfree(DatumGetPointer(column->bv_values[INCLUSION_UNION]));
 
 		if (result == newval)
-			result = datumCopy(result, attr->attbyval, attr->attlen);
+			result = datumCopy(result, DeformAttrByVal(attr), attr->attlen);
 	}
 	column->bv_values[INCLUSION_UNION] = result;
 
@@ -479,7 +479,7 @@ brin_inclusion_union(PG_FUNCTION_ARGS)
 	BrinValues *col_b = (BrinValues *) PG_GETARG_POINTER(2);
 	Oid			colloid = PG_GET_COLLATION();
 	AttrNumber	attno;
-	Form_pg_attribute attr;
+	TupleDescDeformAttr *attr;
 	FmgrInfo   *finfo;
 	Datum		result;
 
@@ -487,7 +487,7 @@ brin_inclusion_union(PG_FUNCTION_ARGS)
 	Assert(!col_a->bv_allnulls && !col_b->bv_allnulls);
 
 	attno = col_a->bv_attno;
-	attr = TupleDescAttr(bdesc->bd_tupdesc, attno - 1);
+	attr = TupleDescDeformAttr(bdesc->bd_tupdesc, attno - 1);
 
 	/* If B includes empty elements, mark A similarly, if needed. */
 	if (!DatumGetBool(col_a->bv_values[INCLUSION_CONTAINS_EMPTY]) &&
@@ -522,13 +522,13 @@ brin_inclusion_union(PG_FUNCTION_ARGS)
 	result = FunctionCall2Coll(finfo, colloid,
 							   col_a->bv_values[INCLUSION_UNION],
 							   col_b->bv_values[INCLUSION_UNION]);
-	if (!attr->attbyval &&
+	if (!DeformAttrByVal(attr) &&
 		DatumGetPointer(result) != DatumGetPointer(col_a->bv_values[INCLUSION_UNION]))
 	{
 		pfree(DatumGetPointer(col_a->bv_values[INCLUSION_UNION]));
 
 		if (result == col_b->bv_values[INCLUSION_UNION])
-			result = datumCopy(result, attr->attbyval, attr->attlen);
+			result = datumCopy(result, DeformAttrByVal(attr), attr->attlen);
 	}
 	col_a->bv_values[INCLUSION_UNION] = result;
 
diff --git a/src/backend/access/brin/brin_tuple.c b/src/backend/access/brin/brin_tuple.c
index 997eb6d822..213b873867 100644
--- a/src/backend/access/brin/brin_tuple.c
+++ b/src/backend/access/brin/brin_tuple.c
@@ -699,7 +699,7 @@ brin_deconstruct_tuple(BrinDesc *brdesc,
 			 datumno < brdesc->bd_info[attnum]->oi_nstored;
 			 datumno++)
 		{
-			Form_pg_attribute thisatt = TupleDescAttr(diskdsc, stored);
+			TupleDescDeformAttr *thisatt = TupleDescDeformAttr(diskdsc, stored);
 
 			if (thisatt->attlen == -1)
 			{
@@ -712,7 +712,7 @@ brin_deconstruct_tuple(BrinDesc *brdesc,
 				off = att_align_nominal(off, thisatt->attalign);
 			}
 
-			values[stored++] = fetchatt(thisatt, tp + off);
+			values[stored++] = fetchatt_fast(thisatt, tp + off);
 
 			off = att_addlength_pointer(off, thisatt->attlen, tp + off);
 		}
diff --git a/src/backend/access/common/attmap.c b/src/backend/access/common/attmap.c
index b0fe27ef57..a341f0bf50 100644
--- a/src/backend/access/common/attmap.c
+++ b/src/backend/access/common/attmap.c
@@ -135,7 +135,9 @@ build_attrmap_by_position(TupleDesc indesc,
 	/* Check for unused input columns */
 	for (; j < indesc->natts; j++)
 	{
-		if (TupleDescAttr(indesc, j)->attisdropped)
+		TupleDescDeformAttr *attr = TupleDescDeformAttr(indesc, j);
+
+		if (DeformAttrIsDropped(attr))
 			continue;
 		nincols++;
 		same = false;			/* we'll complain below */
@@ -299,25 +301,27 @@ check_attrmap_match(TupleDesc indesc,
 
 	for (i = 0; i < attrMap->maplen; i++)
 	{
-		Form_pg_attribute inatt = TupleDescAttr(indesc, i);
-		Form_pg_attribute outatt = TupleDescAttr(outdesc, i);
+		TupleDescDeformAttr *inatt = TupleDescDeformAttr(indesc, i);
+		TupleDescDeformAttr *outatt;
 
 		/*
 		 * If the input column has a missing attribute, we need a conversion.
 		 */
-		if (inatt->atthasmissing)
+		if (DeformAttrHasMissing(inatt))
 			return false;
 
 		if (attrMap->attnums[i] == (i + 1))
 			continue;
 
+		outatt = TupleDescDeformAttr(outdesc, i);
+
 		/*
 		 * If it's a dropped column and the corresponding input column is also
 		 * dropped, we don't need a conversion.  However, attlen and attalign
 		 * must agree.
 		 */
 		if (attrMap->attnums[i] == 0 &&
-			inatt->attisdropped &&
+			DeformAttrIsDropped(inatt) &&
 			inatt->attlen == outatt->attlen &&
 			inatt->attalign == outatt->attalign)
 			continue;
diff --git a/src/backend/access/common/heaptuple.c b/src/backend/access/common/heaptuple.c
index 9e3407bf98..963b88c27b 100644
--- a/src/backend/access/common/heaptuple.c
+++ b/src/backend/access/common/heaptuple.c
@@ -83,6 +83,9 @@
 #define VARLENA_ATT_IS_PACKABLE(att) \
 	((att)->attstorage != TYPSTORAGE_PLAIN)
 
+#define ATT_IS_PACKABLE_FAST(att) \
+	((att)->attlen == -1 && DeformAttrIsPackable(att))
+
 /*
  * Setup for caching pass-by-ref missing attributes in a way that survives
  * tupleDesc destruction.
@@ -147,14 +150,14 @@ Datum
 getmissingattr(TupleDesc tupleDesc,
 			   int attnum, bool *isnull)
 {
-	Form_pg_attribute att;
+	TupleDescDeformAttr *att;
 
 	Assert(attnum <= tupleDesc->natts);
 	Assert(attnum > 0);
 
-	att = TupleDescAttr(tupleDesc, attnum - 1);
+	att = TupleDescDeformAttr(tupleDesc, attnum - 1);
 
-	if (att->atthasmissing)
+	if (DeformAttrHasMissing(att))
 	{
 		AttrMissing *attrmiss;
 
@@ -173,7 +176,7 @@ getmissingattr(TupleDesc tupleDesc,
 			*isnull = false;
 
 			/* no  need to cache by-value attributes */
-			if (att->attbyval)
+			if (DeformAttrByVal(att))
 				return attrmiss->am_value;
 
 			/* set up cache if required */
@@ -223,15 +226,15 @@ heap_compute_data_size(TupleDesc tupleDesc,
 	for (i = 0; i < numberOfAttributes; i++)
 	{
 		Datum		val;
-		Form_pg_attribute atti;
+		TupleDescDeformAttr *atti;
 
 		if (isnull[i])
 			continue;
 
 		val = values[i];
-		atti = TupleDescAttr(tupleDesc, i);
+		atti = TupleDescDeformAttr(tupleDesc, i);
 
-		if (ATT_IS_PACKABLE(atti) &&
+		if (ATT_IS_PACKABLE_FAST(atti) &&
 			VARATT_CAN_MAKE_SHORT(DatumGetPointer(val)))
 		{
 			/*
@@ -268,7 +271,7 @@ heap_compute_data_size(TupleDesc tupleDesc,
  * Fill in either a data value or a bit in the null bitmask
  */
 static inline void
-fill_val(Form_pg_attribute att,
+fill_val(TupleDescDeformAttr *att,
 		 bits8 **bit,
 		 int *bitmask,
 		 char **dataP,
@@ -307,7 +310,7 @@ fill_val(Form_pg_attribute att,
 	 * XXX we use the att_align macros on the pointer value itself, not on an
 	 * offset.  This is a bit of a hack.
 	 */
-	if (att->attbyval)
+	if (DeformAttrByVal(att))
 	{
 		/* pass-by-value */
 		data = (char *) att_align_nominal(data, att->attalign);
@@ -349,7 +352,7 @@ fill_val(Form_pg_attribute att,
 			data_length = VARSIZE_SHORT(val);
 			memcpy(data, val, data_length);
 		}
-		else if (VARLENA_ATT_IS_PACKABLE(att) &&
+		else if (DeformAttrIsPackable(att) &&
 				 VARATT_CAN_MAKE_SHORT(val))
 		{
 			/* convert to short varlena -- no alignment */
@@ -427,7 +430,7 @@ heap_fill_tuple(TupleDesc tupleDesc,
 
 	for (i = 0; i < numberOfAttributes; i++)
 	{
-		Form_pg_attribute attr = TupleDescAttr(tupleDesc, i);
+		TupleDescDeformAttr *attr = TupleDescDeformAttr(tupleDesc, i);
 
 		fill_val(attr,
 				 bitP ? &bitP : NULL,
@@ -461,7 +464,8 @@ heap_attisnull(HeapTuple tup, int attnum, TupleDesc tupleDesc)
 	Assert(!tupleDesc || attnum <= tupleDesc->natts);
 	if (attnum > (int) HeapTupleHeaderGetNatts(tup->t_data))
 	{
-		if (tupleDesc && TupleDescAttr(tupleDesc, attnum - 1)->atthasmissing)
+		if (tupleDesc &&
+			DeformAttrHasMissing(TupleDescDeformAttr(tupleDesc, attnum - 1)))
 			return false;
 		else
 			return true;
@@ -570,15 +574,15 @@ nocachegetattr(HeapTuple tup,
 
 	if (!slow)
 	{
-		Form_pg_attribute att;
+		TupleDescDeformAttr *att;
 
 		/*
 		 * If we get here, there are no nulls up to and including the target
 		 * attribute.  If we have a cached offset, we can use it.
 		 */
-		att = TupleDescAttr(tupleDesc, attnum);
+		att = TupleDescDeformAttr(tupleDesc, attnum);
 		if (att->attcacheoff >= 0)
-			return fetchatt(att, tp + att->attcacheoff);
+			return fetchatt_fast(att, tp + att->attcacheoff);
 
 		/*
 		 * Otherwise, check for non-fixed-length attrs up to and including
@@ -591,7 +595,7 @@ nocachegetattr(HeapTuple tup,
 
 			for (j = 0; j <= attnum; j++)
 			{
-				if (TupleDescAttr(tupleDesc, j)->attlen <= 0)
+				if (TupleDescDeformAttr(tupleDesc, j)->attlen <= 0)
 				{
 					slow = true;
 					break;
@@ -614,18 +618,18 @@ nocachegetattr(HeapTuple tup,
 		 * fixed-width columns, in hope of avoiding future visits to this
 		 * routine.
 		 */
-		TupleDescAttr(tupleDesc, 0)->attcacheoff = 0;
+		TupleDescDeformAttr(tupleDesc, 0)->attcacheoff = 0;
 
 		/* we might have set some offsets in the slow path previously */
-		while (j < natts && TupleDescAttr(tupleDesc, j)->attcacheoff > 0)
+		while (j < natts && TupleDescDeformAttr(tupleDesc, j)->attcacheoff > 0)
 			j++;
 
-		off = TupleDescAttr(tupleDesc, j - 1)->attcacheoff +
-			TupleDescAttr(tupleDesc, j - 1)->attlen;
+		off = TupleDescDeformAttr(tupleDesc, j - 1)->attcacheoff +
+			  TupleDescDeformAttr(tupleDesc, j - 1)->attlen;
 
 		for (; j < natts; j++)
 		{
-			Form_pg_attribute att = TupleDescAttr(tupleDesc, j);
+			TupleDescDeformAttr *att = TupleDescDeformAttr(tupleDesc, j);
 
 			if (att->attlen <= 0)
 				break;
@@ -639,7 +643,7 @@ nocachegetattr(HeapTuple tup,
 
 		Assert(j > attnum);
 
-		off = TupleDescAttr(tupleDesc, attnum)->attcacheoff;
+		off = TupleDescDeformAttr(tupleDesc, attnum)->attcacheoff;
 	}
 	else
 	{
@@ -659,7 +663,7 @@ nocachegetattr(HeapTuple tup,
 		off = 0;
 		for (i = 0;; i++)		/* loop exit is at "break" */
 		{
-			Form_pg_attribute att = TupleDescAttr(tupleDesc, i);
+			TupleDescDeformAttr *att = TupleDescDeformAttr(tupleDesc, i);
 
 			if (HeapTupleHasNulls(tup) && att_isnull(i, bp))
 			{
@@ -707,7 +711,7 @@ nocachegetattr(HeapTuple tup,
 		}
 	}
 
-	return fetchatt(TupleDescAttr(tupleDesc, attnum), tp + off);
+	return fetchatt_fast(TupleDescDeformAttr(tupleDesc, attnum), tp + off);
 }
 
 /* ----------------
@@ -892,7 +896,7 @@ expand_tuple(HeapTuple *targetHeapTuple,
 		{
 			if (attrmiss[attnum].am_present)
 			{
-				Form_pg_attribute att = TupleDescAttr(tupleDesc, attnum);
+				TupleDescDeformAttr *att = TupleDescDeformAttr(tupleDesc, attnum);
 
 				targetDataLen = att_align_datum(targetDataLen,
 												att->attalign,
@@ -1021,7 +1025,7 @@ expand_tuple(HeapTuple *targetHeapTuple,
 	for (attnum = sourceNatts; attnum < natts; attnum++)
 	{
 
-		Form_pg_attribute attr = TupleDescAttr(tupleDesc, attnum);
+		TupleDescDeformAttr *attr = TupleDescDeformAttr(tupleDesc, attnum);
 
 		if (attrmiss && attrmiss[attnum].am_present)
 		{
@@ -1370,7 +1374,7 @@ heap_deform_tuple(HeapTuple tuple, TupleDesc tupleDesc,
 
 	for (attnum = 0; attnum < natts; attnum++)
 	{
-		Form_pg_attribute thisatt = TupleDescAttr(tupleDesc, attnum);
+		TupleDescDeformAttr *thisatt = TupleDescDeformAttr(tupleDesc, attnum);
 
 		if (hasnulls && att_isnull(attnum, bp))
 		{
@@ -1411,7 +1415,7 @@ heap_deform_tuple(HeapTuple tuple, TupleDesc tupleDesc,
 				thisatt->attcacheoff = off;
 		}
 
-		values[attnum] = fetchatt(thisatt, tp + off);
+		values[attnum] = fetchatt_fast(thisatt, tp + off);
 
 		off = att_addlength_pointer(off, thisatt->attlen, tp + off);
 
diff --git a/src/backend/access/common/indextuple.c b/src/backend/access/common/indextuple.c
index 79ae29989d..aac4214cc7 100644
--- a/src/backend/access/common/indextuple.c
+++ b/src/backend/access/common/indextuple.c
@@ -303,15 +303,15 @@ nocache_index_getattr(IndexTuple tup,
 
 	if (!slow)
 	{
-		Form_pg_attribute att;
+		TupleDescDeformAttr *att;
 
 		/*
 		 * If we get here, there are no nulls up to and including the target
 		 * attribute.  If we have a cached offset, we can use it.
 		 */
-		att = TupleDescAttr(tupleDesc, attnum);
+		att = TupleDescDeformAttr(tupleDesc, attnum);
 		if (att->attcacheoff >= 0)
-			return fetchatt(att, tp + att->attcacheoff);
+			return fetchatt_fast(att, tp + att->attcacheoff);
 
 		/*
 		 * Otherwise, check for non-fixed-length attrs up to and including
@@ -324,7 +324,7 @@ nocache_index_getattr(IndexTuple tup,
 
 			for (j = 0; j <= attnum; j++)
 			{
-				if (TupleDescAttr(tupleDesc, j)->attlen <= 0)
+				if (TupleDescDeformAttr(tupleDesc, j)->attlen <= 0)
 				{
 					slow = true;
 					break;
@@ -347,18 +347,18 @@ nocache_index_getattr(IndexTuple tup,
 		 * fixed-width columns, in hope of avoiding future visits to this
 		 * routine.
 		 */
-		TupleDescAttr(tupleDesc, 0)->attcacheoff = 0;
+		TupleDescDeformAttr(tupleDesc, 0)->attcacheoff = 0;
 
 		/* we might have set some offsets in the slow path previously */
-		while (j < natts && TupleDescAttr(tupleDesc, j)->attcacheoff > 0)
+		while (j < natts && TupleDescDeformAttr(tupleDesc, j)->attcacheoff > 0)
 			j++;
 
-		off = TupleDescAttr(tupleDesc, j - 1)->attcacheoff +
-			TupleDescAttr(tupleDesc, j - 1)->attlen;
+		off = TupleDescDeformAttr(tupleDesc, j - 1)->attcacheoff +
+			TupleDescDeformAttr(tupleDesc, j - 1)->attlen;
 
 		for (; j < natts; j++)
 		{
-			Form_pg_attribute att = TupleDescAttr(tupleDesc, j);
+			TupleDescDeformAttr *att = TupleDescDeformAttr(tupleDesc, j);
 
 			if (att->attlen <= 0)
 				break;
@@ -372,7 +372,7 @@ nocache_index_getattr(IndexTuple tup,
 
 		Assert(j > attnum);
 
-		off = TupleDescAttr(tupleDesc, attnum)->attcacheoff;
+		off = TupleDescDeformAttr(tupleDesc, attnum)->attcacheoff;
 	}
 	else
 	{
@@ -392,7 +392,7 @@ nocache_index_getattr(IndexTuple tup,
 		off = 0;
 		for (i = 0;; i++)		/* loop exit is at "break" */
 		{
-			Form_pg_attribute att = TupleDescAttr(tupleDesc, i);
+			TupleDescDeformAttr *att = TupleDescDeformAttr(tupleDesc, i);
 
 			if (IndexTupleHasNulls(tup) && att_isnull(i, bp))
 			{
@@ -440,7 +440,7 @@ nocache_index_getattr(IndexTuple tup,
 		}
 	}
 
-	return fetchatt(TupleDescAttr(tupleDesc, attnum), tp + off);
+	return fetchatt_fast(TupleDescDeformAttr(tupleDesc, attnum), tp + off);
 }
 
 /*
@@ -490,7 +490,7 @@ index_deform_tuple_internal(TupleDesc tupleDescriptor,
 
 	for (attnum = 0; attnum < natts; attnum++)
 	{
-		Form_pg_attribute thisatt = TupleDescAttr(tupleDescriptor, attnum);
+		TupleDescDeformAttr *thisatt = TupleDescDeformAttr(tupleDescriptor, attnum);
 
 		if (hasnulls && att_isnull(attnum, bp))
 		{
@@ -531,7 +531,7 @@ index_deform_tuple_internal(TupleDesc tupleDescriptor,
 				thisatt->attcacheoff = off;
 		}
 
-		values[attnum] = fetchatt(thisatt, tp + off);
+		values[attnum] = fetchatt_fast(thisatt, tp + off);
 
 		off = att_addlength_pointer(off, thisatt->attlen, tp + off);
 
diff --git a/src/backend/access/common/tupdesc.c b/src/backend/access/common/tupdesc.c
index fba0026520..ee996f465a 100644
--- a/src/backend/access/common/tupdesc.c
+++ b/src/backend/access/common/tupdesc.c
@@ -56,6 +56,30 @@ ResourceOwnerForgetTupleDesc(ResourceOwner owner, TupleDesc tupdesc)
 	ResourceOwnerForget(owner, PointerGetDatum(tupdesc), &tupdesc_resowner_desc);
 }
 
+void
+populate_TupleDescAttr(TupleDescDeformAttr *dst, Form_pg_attribute src)
+{
+	dst->attcacheoff = -1;
+	dst->attlen = src->attlen;
+
+	dst->attflags = 0;
+
+	if (src->attbyval)
+		dst->attflags |= DEFORM_ATTR_FLAG_BYVAL;
+	if (src->attstorage != TYPSTORAGE_PLAIN)
+		dst->attflags |= DEFORM_ATTR_FLAG_IS_PACKABLE;
+	if (src->atthasmissing)
+		dst->attflags |= DEFORM_ATTR_FLAG_HAS_MISSING;
+	if (src->attisdropped)
+		dst->attflags |= DEFORM_ATTR_FLAG_IS_DROPPED;
+	if (src->attgenerated)
+		dst->attflags |= DEFORM_ATTR_FLAG_IS_GENERATED;
+	if (src->attnotnull)
+		dst->attflags |= DEFORM_ATTR_FLAG_IS_NOTNULL;
+
+	dst->attalign = src->attalign;
+}
+
 /*
  * CreateTemplateTupleDesc
  *		This function allocates an empty tuple descriptor structure.
@@ -85,7 +109,8 @@ CreateTemplateTupleDesc(int natts)
 	 * could be less due to trailing padding, although with the current
 	 * definition of pg_attribute there probably isn't any padding.
 	 */
-	desc = (TupleDesc) palloc(MAXALIGN(sizeof(TupleDescData)) +
+	desc = (TupleDesc) palloc(offsetof(struct TupleDescData, deform_attrs) +
+							  natts * sizeof(TupleDescDeformAttr) +
 							  natts * sizeof(FormData_pg_attribute));
 
 	/*
@@ -118,8 +143,11 @@ CreateTupleDesc(int natts, Form_pg_attribute *attrs)
 	desc = CreateTemplateTupleDesc(natts);
 
 	for (i = 0; i < natts; ++i)
+	{
 		memcpy(TupleDescAttr(desc, i), attrs[i], ATTRIBUTE_FIXED_PART_SIZE);
-
+		populate_TupleDescAttr(TupleDescDeformAttr(desc, i),
+							   TupleDescAttr(desc, i));
+	}
 	return desc;
 }
 
@@ -156,6 +184,9 @@ CreateTupleDescCopy(TupleDesc tupdesc)
 		att->atthasmissing = false;
 		att->attidentity = '\0';
 		att->attgenerated = '\0';
+
+		populate_TupleDescAttr(TupleDescDeformAttr(desc, i),
+							   TupleDescAttr(desc, i));
 	}
 
 	/* We can copy the tuple type identification, too */
@@ -184,6 +215,10 @@ CreateTupleDescCopyConstr(TupleDesc tupdesc)
 		   TupleDescAttr(tupdesc, 0),
 		   desc->natts * sizeof(FormData_pg_attribute));
 
+	for (i = 0; i < desc->natts; i++)
+		populate_TupleDescAttr(TupleDescDeformAttr(desc, i),
+							   TupleDescAttr(desc, i));
+
 	/* Copy the TupleConstr data structure, if any */
 	if (constr)
 	{
@@ -208,10 +243,10 @@ CreateTupleDescCopyConstr(TupleDesc tupdesc)
 			{
 				if (constr->missing[i].am_present)
 				{
-					Form_pg_attribute attr = TupleDescAttr(tupdesc, i);
+					TupleDescDeformAttr *attr = TupleDescDeformAttr(tupdesc, i);
 
 					cpy->missing[i].am_value = datumCopy(constr->missing[i].am_value,
-														 attr->attbyval,
+														 DeformAttrByVal(attr),
 														 attr->attlen);
 				}
 			}
@@ -275,6 +310,9 @@ TupleDescCopy(TupleDesc dst, TupleDesc src)
 		att->atthasmissing = false;
 		att->attidentity = '\0';
 		att->attgenerated = '\0';
+
+		populate_TupleDescAttr(TupleDescDeformAttr(dst, i),
+							   TupleDescAttr(dst, i));
 	}
 	dst->constr = NULL;
 
@@ -329,6 +367,8 @@ TupleDescCopyEntry(TupleDesc dst, AttrNumber dstAttno,
 	dstAtt->atthasmissing = false;
 	dstAtt->attidentity = '\0';
 	dstAtt->attgenerated = '\0';
+
+	populate_TupleDescAttr(TupleDescDeformAttr(dst, dstAttno - 1), dstAtt);
 }
 
 /*
@@ -528,10 +568,10 @@ equalTupleDescs(TupleDesc tupdesc1, TupleDesc tupdesc2)
 					return false;
 				if (missval1->am_present)
 				{
-					Form_pg_attribute missatt1 = TupleDescAttr(tupdesc1, i);
+					TupleDescDeformAttr *missatt1 = TupleDescDeformAttr(tupdesc1, i);
 
 					if (!datumIsEqual(missval1->am_value, missval2->am_value,
-									  missatt1->attbyval, missatt1->attlen))
+									  DeformAttrByVal(missatt1), missatt1->attlen))
 						return false;
 				}
 			}
@@ -721,6 +761,9 @@ TupleDescInitEntry(TupleDesc desc,
 	att->attcompression = InvalidCompressionMethod;
 	att->attcollation = typeForm->typcollation;
 
+	populate_TupleDescAttr(TupleDescDeformAttr(desc, attributeNumber - 1),
+						   att);
+
 	ReleaseSysCache(tuple);
 }
 
@@ -828,6 +871,9 @@ TupleDescInitBuiltinEntry(TupleDesc desc,
 		default:
 			elog(ERROR, "unsupported type %u", oidtypeid);
 	}
+
+	populate_TupleDescAttr(TupleDescDeformAttr(desc, attributeNumber - 1),
+						   att);
 }
 
 /*
diff --git a/src/backend/access/gin/ginbulk.c b/src/backend/access/gin/ginbulk.c
index 7f89cd5e82..c2c03261fa 100644
--- a/src/backend/access/gin/ginbulk.c
+++ b/src/backend/access/gin/ginbulk.c
@@ -127,11 +127,11 @@ ginInitBA(BuildAccumulator *accum)
 static Datum
 getDatumCopy(BuildAccumulator *accum, OffsetNumber attnum, Datum value)
 {
-	Form_pg_attribute att;
+	TupleDescDeformAttr *att;
 	Datum		res;
 
-	att = TupleDescAttr(accum->ginstate->origTupdesc, attnum - 1);
-	if (att->attbyval)
+	att = TupleDescDeformAttr(accum->ginstate->origTupdesc, attnum - 1);
+	if (DeformAttrByVal(att))
 		res = value;
 	else
 	{
diff --git a/src/backend/access/gin/ginget.c b/src/backend/access/gin/ginget.c
index 0b4f2ebadb..d869cf2d77 100644
--- a/src/backend/access/gin/ginget.c
+++ b/src/backend/access/gin/ginget.c
@@ -122,7 +122,7 @@ collectMatchBitmap(GinBtreeData *btree, GinBtreeStack *stack,
 				   GinScanEntry scanEntry, Snapshot snapshot)
 {
 	OffsetNumber attnum;
-	Form_pg_attribute attr;
+	TupleDescDeformAttr *attr;
 
 	/* Initialize empty bitmap result */
 	scanEntry->matchBitmap = tbm_create(work_mem * 1024L, NULL);
@@ -134,7 +134,7 @@ collectMatchBitmap(GinBtreeData *btree, GinBtreeStack *stack,
 
 	/* Locate tupdesc entry for key column (for attbyval/attlen data) */
 	attnum = scanEntry->attnum;
-	attr = TupleDescAttr(btree->ginstate->origTupdesc, attnum - 1);
+	attr = TupleDescDeformAttr(btree->ginstate->origTupdesc, attnum - 1);
 
 	/*
 	 * Predicate lock entry leaf page, following pages will be locked by
@@ -232,7 +232,7 @@ collectMatchBitmap(GinBtreeData *btree, GinBtreeStack *stack,
 			 * tuple after re-locking
 			 */
 			if (icategory == GIN_CAT_NORM_KEY)
-				idatum = datumCopy(idatum, attr->attbyval, attr->attlen);
+				idatum = datumCopy(idatum, DeformAttrByVal(attr), attr->attlen);
 
 			LockBuffer(stack->buffer, GIN_UNLOCK);
 
@@ -291,7 +291,7 @@ collectMatchBitmap(GinBtreeData *btree, GinBtreeStack *stack,
 				stack->off++;
 			}
 
-			if (icategory == GIN_CAT_NORM_KEY && !attr->attbyval)
+			if (icategory == GIN_CAT_NORM_KEY && !DeformAttrByVal(attr))
 				pfree(DatumGetPointer(idatum));
 		}
 		else
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index ba06df30fa..0779948dbd 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -657,10 +657,12 @@ gistInitBuffering(GISTBuildState *buildstate)
 	itupMinSize = (Size) MAXALIGN(sizeof(IndexTupleData));
 	for (i = 0; i < index->rd_att->natts; i++)
 	{
-		if (TupleDescAttr(index->rd_att, i)->attlen < 0)
+		TupleDescDeformAttr *attr = TupleDescDeformAttr(index->rd_att, i);
+
+		if (attr->attlen < 0)
 			itupMinSize += VARHDRSZ;
 		else
-			itupMinSize += TupleDescAttr(index->rd_att, i)->attlen;
+			itupMinSize += attr->attlen;
 	}
 
 	/* Calculate average and maximal number of index tuples which fit to page */
diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index 91b20147a0..205e601642 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -4080,8 +4080,6 @@ static bool
 heap_attr_equals(TupleDesc tupdesc, int attrnum, Datum value1, Datum value2,
 				 bool isnull1, bool isnull2)
 {
-	Form_pg_attribute att;
-
 	/*
 	 * If one value is NULL and other is not, then they are certainly not
 	 * equal
@@ -4111,9 +4109,11 @@ heap_attr_equals(TupleDesc tupdesc, int attrnum, Datum value1, Datum value2,
 	}
 	else
 	{
+		TupleDescDeformAttr *att;
+
 		Assert(attrnum <= tupdesc->natts);
-		att = TupleDescAttr(tupdesc, attrnum - 1);
-		return datumIsEqual(value1, value2, att->attbyval, att->attlen);
+		att = TupleDescDeformAttr(tupdesc, attrnum - 1);
+		return datumIsEqual(value1, value2, DeformAttrByVal(att), att->attlen);
 	}
 }
 
@@ -4194,7 +4194,7 @@ HeapDetermineColumnsInfo(Relation relation,
 		 * that system attributes can't be stored externally.
 		 */
 		if (attrnum < 0 || isnull1 ||
-			TupleDescAttr(tupdesc, attrnum - 1)->attlen != -1)
+			TupleDescDeformAttr(tupdesc, attrnum - 1)->attlen != -1)
 			continue;
 
 		/*
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 6f8b1b7929..16d6a62ee1 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -2524,7 +2524,7 @@ reform_and_rewrite_tuple(HeapTuple tuple,
 	/* Be sure to null out any dropped columns */
 	for (i = 0; i < newTupDesc->natts; i++)
 	{
-		if (TupleDescAttr(newTupDesc, i)->attisdropped)
+		if (DeformAttrIsDropped(TupleDescDeformAttr(newTupDesc, i)))
 			isnull[i] = true;
 	}
 
diff --git a/src/backend/access/heap/heaptoast.c b/src/backend/access/heap/heaptoast.c
index a420e16530..ffc8d15d31 100644
--- a/src/backend/access/heap/heaptoast.c
+++ b/src/backend/access/heap/heaptoast.c
@@ -369,7 +369,7 @@ toast_flatten_tuple(HeapTuple tup, TupleDesc tupleDesc)
 		/*
 		 * Look at non-null varlena attributes
 		 */
-		if (!toast_isnull[i] && TupleDescAttr(tupleDesc, i)->attlen == -1)
+		if (!toast_isnull[i] && TupleDescDeformAttr(tupleDesc, i)->attlen == -1)
 		{
 			struct varlena *new_value;
 
@@ -483,7 +483,7 @@ toast_flatten_tuple_to_datum(HeapTupleHeader tup,
 		 */
 		if (toast_isnull[i])
 			has_nulls = true;
-		else if (TupleDescAttr(tupleDesc, i)->attlen == -1)
+		else if (TupleDescDeformAttr(tupleDesc, i)->attlen == -1)
 		{
 			struct varlena *new_value;
 
@@ -584,7 +584,7 @@ toast_build_flattened_tuple(TupleDesc tupleDesc,
 		/*
 		 * Look at non-null varlena attributes
 		 */
-		if (!isnull[i] && TupleDescAttr(tupleDesc, i)->attlen == -1)
+		if (!isnull[i] && TupleDescDeformAttr(tupleDesc, i)->attlen == -1)
 		{
 			struct varlena *new_value;
 
diff --git a/src/backend/access/nbtree/nbtutils.c b/src/backend/access/nbtree/nbtutils.c
index d6de2072d4..bfc9daff41 100644
--- a/src/backend/access/nbtree/nbtutils.c
+++ b/src/backend/access/nbtree/nbtutils.c
@@ -4874,17 +4874,17 @@ _bt_keep_natts_fast(Relation rel, IndexTuple lastleft, IndexTuple firstright)
 					datum2;
 		bool		isNull1,
 					isNull2;
-		Form_pg_attribute att;
+		TupleDescDeformAttr *att;
 
 		datum1 = index_getattr(lastleft, attnum, itupdesc, &isNull1);
 		datum2 = index_getattr(firstright, attnum, itupdesc, &isNull2);
-		att = TupleDescAttr(itupdesc, attnum - 1);
+		att = TupleDescDeformAttr(itupdesc, attnum - 1);
 
 		if (isNull1 != isNull2)
 			break;
 
 		if (!isNull1 &&
-			!datum_image_eq(datum1, datum2, att->attbyval, att->attlen))
+			!datum_image_eq(datum1, datum2, DeformAttrByVal(att), att->attlen))
 			break;
 
 		keepnatts++;
diff --git a/src/backend/access/spgist/spgdoinsert.c b/src/backend/access/spgist/spgdoinsert.c
index a4995c168b..1cac593ec4 100644
--- a/src/backend/access/spgist/spgdoinsert.c
+++ b/src/backend/access/spgist/spgdoinsert.c
@@ -1974,7 +1974,7 @@ spgdoinsert(Relation index, SpGistState *state,
 	{
 		if (!isnulls[i])
 		{
-			if (TupleDescAttr(leafDescriptor, i)->attlen == -1)
+			if (TupleDescDeformAttr(leafDescriptor, i)->attlen == -1)
 				leafDatums[i] = PointerGetDatum(PG_DETOAST_DATUM(datums[i]));
 			else
 				leafDatums[i] = datums[i];
diff --git a/src/backend/access/table/toast_helper.c b/src/backend/access/table/toast_helper.c
index 53224932f0..361f0cbd88 100644
--- a/src/backend/access/table/toast_helper.c
+++ b/src/backend/access/table/toast_helper.c
@@ -324,7 +324,7 @@ toast_delete_external(Relation rel, const Datum *values, const bool *isnull,
 
 	for (i = 0; i < numAttrs; i++)
 	{
-		if (TupleDescAttr(tupleDesc, i)->attlen == -1)
+		if (TupleDescDeformAttr(tupleDesc, i)->attlen == -1)
 		{
 			Datum		value = values[i];
 
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index a819b4197c..b59ba2b45c 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -477,6 +477,8 @@ ConstructTupleDescriptor(Relation heapRelation,
 
 			ReleaseSysCache(tuple);
 		}
+
+		populate_TupleDescAttr(TupleDescDeformAttr(indexTupDesc, i), to);
 	}
 
 	pfree(amroutine);
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index df7a4a21c9..ecd2044a15 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -867,9 +867,9 @@ CopyGetAttnums(TupleDesc tupDesc, Relation rel, List *attnamelist)
 
 		for (i = 0; i < attr_count; i++)
 		{
-			if (TupleDescAttr(tupDesc, i)->attisdropped)
-				continue;
-			if (TupleDescAttr(tupDesc, i)->attgenerated)
+			TupleDescDeformAttr *attr = TupleDescDeformAttr(tupDesc, i);
+
+			if (DeformAttrIsDropped(attr) || DeformAttrIsGenerated(attr))
 				continue;
 			attnums = lappend_int(attnums, i + 1);
 		}
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 8fcb188323..96034cdefc 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -944,6 +944,9 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 			cookedDefaults = lappend(cookedDefaults, cooked);
 			attr->atthasdef = true;
 		}
+
+		populate_TupleDescAttr(TupleDescDeformAttr(descriptor, attnum - 1),
+							   attr);
 	}
 
 	/*
@@ -1350,6 +1353,8 @@ BuildDescForRelation(const List *columns)
 			att->attstorage = entry->storage;
 		else if (entry->storage_name)
 			att->attstorage = GetAttributeStorage(att->atttypid, entry->storage_name);
+
+		populate_TupleDescAttr(TupleDescDeformAttr(desc, attnum - 1), att);
 	}
 
 	if (has_not_null)
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index ccd4863778..4d87186ce9 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -594,9 +594,9 @@ ExecBuildUpdateProjection(List *targetList,
 	 */
 	for (int attnum = relDesc->natts; attnum > 0; attnum--)
 	{
-		Form_pg_attribute attr = TupleDescAttr(relDesc, attnum - 1);
+		TupleDescDeformAttr *attr = TupleDescDeformAttr(relDesc, attnum - 1);
 
-		if (attr->attisdropped)
+		if (DeformAttrIsDropped(attr))
 			continue;
 		if (bms_is_member(attnum, assignedCols))
 			continue;
@@ -690,9 +690,9 @@ ExecBuildUpdateProjection(List *targetList,
 	 */
 	for (int attnum = 1; attnum <= relDesc->natts; attnum++)
 	{
-		Form_pg_attribute attr = TupleDescAttr(relDesc, attnum - 1);
+		TupleDescDeformAttr *attr = TupleDescDeformAttr(relDesc, attnum - 1);
 
-		if (attr->attisdropped)
+		if (DeformAttrIsDropped(attr))
 		{
 			/* Put a null into the ExprState's resvalue/resnull ... */
 			scratch.opcode = EEOP_CONST;
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index d8735286c4..22440cd10b 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -2798,7 +2798,7 @@ ExecEvalRowNullInt(ExprState *state, ExprEvalStep *op,
 	for (int att = 1; att <= tupDesc->natts; att++)
 	{
 		/* ignore dropped columns */
-		if (TupleDescAttr(tupDesc, att - 1)->attisdropped)
+		if (DeformAttrIsDropped(TupleDescDeformAttr(tupDesc, att - 1)))
 			continue;
 		if (heap_attisnull(&tmptup, att, tupDesc))
 		{
@@ -4838,10 +4838,10 @@ ExecEvalWholeRowVar(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
 
 		for (int i = 0; i < var_tupdesc->natts; i++)
 		{
-			Form_pg_attribute vattr = TupleDescAttr(var_tupdesc, i);
-			Form_pg_attribute sattr = TupleDescAttr(tupleDesc, i);
+			TupleDescDeformAttr *vattr = TupleDescDeformAttr(var_tupdesc, i);
+			TupleDescDeformAttr *sattr = TupleDescDeformAttr(tupleDesc, i);
 
-			if (!vattr->attisdropped)
+			if (!DeformAttrIsDropped(vattr))
 				continue;		/* already checked non-dropped cols */
 			if (slot->tts_isnull[i])
 				continue;		/* null is always okay */
diff --git a/src/backend/executor/execJunk.c b/src/backend/executor/execJunk.c
index b962c31383..bff4213a6c 100644
--- a/src/backend/executor/execJunk.c
+++ b/src/backend/executor/execJunk.c
@@ -169,7 +169,7 @@ ExecInitJunkFilterConversion(List *targetList,
 		t = list_head(targetList);
 		for (i = 0; i < cleanLength; i++)
 		{
-			if (TupleDescAttr(cleanTupType, i)->attisdropped)
+			if (DeformAttrIsDropped(TupleDescDeformAttr(cleanTupType, i)))
 				continue;		/* map entry is already zero */
 			for (;;)
 			{
diff --git a/src/backend/executor/execTuples.c b/src/backend/executor/execTuples.c
index 00dc339615..e651934918 100644
--- a/src/backend/executor/execTuples.c
+++ b/src/backend/executor/execTuples.c
@@ -187,10 +187,10 @@ tts_virtual_materialize(TupleTableSlot *slot)
 	/* compute size of memory required */
 	for (int natt = 0; natt < desc->natts; natt++)
 	{
-		Form_pg_attribute att = TupleDescAttr(desc, natt);
+		TupleDescDeformAttr *att = TupleDescDeformAttr(desc, natt);
 		Datum		val;
 
-		if (att->attbyval || slot->tts_isnull[natt])
+		if (DeformAttrByVal(att) || slot->tts_isnull[natt])
 			continue;
 
 		val = slot->tts_values[natt];
@@ -223,10 +223,10 @@ tts_virtual_materialize(TupleTableSlot *slot)
 	/* and copy all attributes into the pre-allocated space */
 	for (int natt = 0; natt < desc->natts; natt++)
 	{
-		Form_pg_attribute att = TupleDescAttr(desc, natt);
+		TupleDescDeformAttr *att = TupleDescDeformAttr(desc, natt);
 		Datum		val;
 
-		if (att->attbyval || slot->tts_isnull[natt])
+		if (DeformAttrByVal(att) || slot->tts_isnull[natt])
 			continue;
 
 		val = slot->tts_values[natt];
@@ -1044,7 +1044,7 @@ slot_deform_heap_tuple(TupleTableSlot *slot, HeapTuple tuple, uint32 *offp,
 
 	for (; attnum < natts; attnum++)
 	{
-		Form_pg_attribute thisatt = TupleDescAttr(tupleDesc, attnum);
+		TupleDescDeformAttr *thisatt = TupleDescDeformAttr(tupleDesc, attnum);
 
 		if (hasnulls && att_isnull(attnum, bp))
 		{
@@ -1085,7 +1085,7 @@ slot_deform_heap_tuple(TupleTableSlot *slot, HeapTuple tuple, uint32 *offp,
 				thisatt->attcacheoff = off;
 		}
 
-		values[attnum] = fetchatt(thisatt, tp + off);
+		values[attnum] = fetchatt_fast(thisatt, tp + off);
 
 		off = att_addlength_pointer(off, thisatt->attlen, tp + off);
 
@@ -2237,7 +2237,7 @@ BuildTupleFromCStrings(AttInMetadata *attinmeta, char **values)
 	 */
 	for (i = 0; i < natts; i++)
 	{
-		if (!TupleDescAttr(tupdesc, i)->attisdropped)
+		if (!DeformAttrIsDropped(TupleDescDeformAttr(tupdesc, i)))
 		{
 			/* Non-dropped attributes */
 			dvalues[i] = InputFunctionCall(&attinmeta->attinfuncs[i],
diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c
index 539cd0a999..7cd37fde34 100644
--- a/src/backend/executor/functions.c
+++ b/src/backend/executor/functions.c
@@ -1885,7 +1885,8 @@ check_sql_fn_retval(List *queryTreeLists,
 		/* remaining columns in rettupdesc had better all be dropped */
 		for (colindex++; colindex <= tupnatts; colindex++)
 		{
-			if (!TupleDescAttr(rettupdesc, colindex - 1)->attisdropped)
+			if (!DeformAttrIsDropped(TupleDescDeformAttr(rettupdesc,
+														colindex - 1)))
 				ereport(ERROR,
 						(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
 						 errmsg("return type mismatch in function declared to return %s",
diff --git a/src/backend/executor/nodeMemoize.c b/src/backend/executor/nodeMemoize.c
index df8e3fff08..d73a4bb0c2 100644
--- a/src/backend/executor/nodeMemoize.c
+++ b/src/backend/executor/nodeMemoize.c
@@ -175,12 +175,12 @@ MemoizeHash_hash(struct memoize_hash *tb, const MemoizeKey *key)
 
 			if (!pslot->tts_isnull[i])	/* treat nulls as having hash key 0 */
 			{
-				Form_pg_attribute attr;
+				TupleDescDeformAttr *attr;
 				uint32		hkey;
 
-				attr = TupleDescAttr(pslot->tts_tupleDescriptor, i);
+				attr = TupleDescDeformAttr(pslot->tts_tupleDescriptor, i);
 
-				hkey = datum_image_hash(pslot->tts_values[i], attr->attbyval, attr->attlen);
+				hkey = datum_image_hash(pslot->tts_values[i], DeformAttrByVal(attr), attr->attlen);
 
 				hashkey ^= hkey;
 			}
@@ -242,7 +242,7 @@ MemoizeHash_equal(struct memoize_hash *tb, const MemoizeKey *key1,
 
 		for (int i = 0; i < numkeys; i++)
 		{
-			Form_pg_attribute attr;
+			TupleDescDeformAttr *attr;
 
 			if (tslot->tts_isnull[i] != pslot->tts_isnull[i])
 			{
@@ -255,9 +255,9 @@ MemoizeHash_equal(struct memoize_hash *tb, const MemoizeKey *key1,
 				continue;
 
 			/* perform binary comparison on the two datums */
-			attr = TupleDescAttr(tslot->tts_tupleDescriptor, i);
+			attr = TupleDescDeformAttr(tslot->tts_tupleDescriptor, i);
 			if (!datum_image_eq(tslot->tts_values[i], pslot->tts_values[i],
-								attr->attbyval, attr->attlen))
+								DeformAttrByVal(attr), attr->attlen))
 			{
 				match = false;
 				break;
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index a2442b7b0d..bf8d1e9702 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -495,14 +495,14 @@ ExecComputeStoredGenerated(ResultRelInfo *resultRelInfo,
 
 	for (int i = 0; i < natts; i++)
 	{
-		Form_pg_attribute attr = TupleDescAttr(tupdesc, i);
+		TupleDescDeformAttr *attr = TupleDescDeformAttr(tupdesc, i);
 
 		if (ri_GeneratedExprs[i])
 		{
 			Datum		val;
 			bool		isnull;
 
-			Assert(attr->attgenerated == ATTRIBUTE_GENERATED_STORED);
+			Assert(TupleDescAttr(tupdesc, i)->attgenerated == ATTRIBUTE_GENERATED_STORED);
 
 			econtext->ecxt_scantuple = slot;
 
@@ -513,7 +513,7 @@ ExecComputeStoredGenerated(ResultRelInfo *resultRelInfo,
 			 * memory for a pass-by-reference Datum is located.
 			 */
 			if (!isnull)
-				val = datumCopy(val, attr->attbyval, attr->attlen);
+				val = datumCopy(val, DeformAttrByVal(attr), attr->attlen);
 
 			values[i] = val;
 			nulls[i] = isnull;
@@ -521,7 +521,7 @@ ExecComputeStoredGenerated(ResultRelInfo *resultRelInfo,
 		else
 		{
 			if (!nulls[i])
-				values[i] = datumCopy(slot->tts_values[i], attr->attbyval, attr->attlen);
+				values[i] = datumCopy(slot->tts_values[i], DeformAttrByVal(attr), attr->attlen);
 		}
 	}
 
diff --git a/src/backend/executor/nodeValuesscan.c b/src/backend/executor/nodeValuesscan.c
index 92948917a0..2ab8c52ead 100644
--- a/src/backend/executor/nodeValuesscan.c
+++ b/src/backend/executor/nodeValuesscan.c
@@ -142,8 +142,8 @@ ValuesNext(ValuesScanState *node)
 		foreach(lc, exprstatelist)
 		{
 			ExprState  *estate = (ExprState *) lfirst(lc);
-			Form_pg_attribute attr = TupleDescAttr(slot->tts_tupleDescriptor,
-												   resind);
+			TupleDescDeformAttr *attr = TupleDescDeformAttr(slot->tts_tupleDescriptor,
+															resind);
 
 			values[resind] = ExecEvalExpr(estate,
 										  econtext,
diff --git a/src/backend/executor/tstoreReceiver.c b/src/backend/executor/tstoreReceiver.c
index de4646b5c2..616530b5b6 100644
--- a/src/backend/executor/tstoreReceiver.c
+++ b/src/backend/executor/tstoreReceiver.c
@@ -65,9 +65,9 @@ tstoreStartupReceiver(DestReceiver *self, int operation, TupleDesc typeinfo)
 	{
 		for (i = 0; i < natts; i++)
 		{
-			Form_pg_attribute attr = TupleDescAttr(typeinfo, i);
+			TupleDescDeformAttr *attr = TupleDescDeformAttr(typeinfo, i);
 
-			if (attr->attisdropped)
+			if (DeformAttrIsDropped(attr))
 				continue;
 			if (attr->attlen == -1)
 			{
@@ -154,9 +154,9 @@ tstoreReceiveSlot_detoast(TupleTableSlot *slot, DestReceiver *self)
 	for (i = 0; i < natts; i++)
 	{
 		Datum		val = slot->tts_values[i];
-		Form_pg_attribute attr = TupleDescAttr(typeinfo, i);
+		TupleDescDeformAttr *attr = TupleDescDeformAttr(typeinfo, i);
 
-		if (!attr->attisdropped && attr->attlen == -1 && !slot->tts_isnull[i])
+		if (!DeformAttrIsDropped(attr) && attr->attlen == -1 && !slot->tts_isnull[i])
 		{
 			if (VARATT_IS_EXTERNAL(DatumGetPointer(val)))
 			{
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 9efdd844aa..5e1c452753 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -174,19 +174,19 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
 	{
 		for (int i = 0; i < relation->rd_att->natts; i++)
 		{
-			Form_pg_attribute attr = TupleDescAttr(relation->rd_att, i);
+			TupleDescDeformAttr *attr = TupleDescDeformAttr(relation->rd_att, i);
 
-			if (attr->attnotnull)
+			if (DeformAttrIsNotNull(attr))
 			{
 				rel->notnullattnums = bms_add_member(rel->notnullattnums,
-													 attr->attnum);
+													 i + 1);
 
 				/*
 				 * Per RemoveAttributeById(), dropped columns will have their
 				 * attnotnull unset, so we needn't check for dropped columns
 				 * in the above condition.
 				 */
-				Assert(!attr->attisdropped);
+				Assert(!DeformAttrIsDropped(attr));
 			}
 		}
 	}
diff --git a/src/backend/replication/pgoutput/pgoutput.c b/src/backend/replication/pgoutput/pgoutput.c
index abef4eaf68..02d0211911 100644
--- a/src/backend/replication/pgoutput/pgoutput.c
+++ b/src/backend/replication/pgoutput/pgoutput.c
@@ -1305,7 +1305,7 @@ pgoutput_row_filter(Relation relation, TupleTableSlot *old_slot,
 	 */
 	for (i = 0; i < desc->natts; i++)
 	{
-		Form_pg_attribute att = TupleDescAttr(desc, i);
+		TupleDescDeformAttr *att = TupleDescDeformAttr(desc, i);
 
 		/*
 		 * if the column in the new tuple or old tuple is null, nothing to do
diff --git a/src/backend/utils/adt/expandedrecord.c b/src/backend/utils/adt/expandedrecord.c
index 7e85ae3825..d0b28d48fb 100644
--- a/src/backend/utils/adt/expandedrecord.c
+++ b/src/backend/utils/adt/expandedrecord.c
@@ -699,10 +699,10 @@ ER_get_flat_size(ExpandedObjectHeader *eohptr)
 	{
 		for (i = 0; i < erh->nfields; i++)
 		{
-			Form_pg_attribute attr = TupleDescAttr(tupdesc, i);
+			TupleDescDeformAttr *attr = TupleDescDeformAttr(tupdesc, i);
 
 			if (!erh->dnulls[i] &&
-				!attr->attbyval && attr->attlen == -1 &&
+				!DeformAttrByVal(attr) && attr->attlen == -1 &&
 				VARATT_IS_EXTERNAL(DatumGetPointer(erh->dvalues[i])))
 			{
 				/*
@@ -1115,7 +1115,7 @@ expanded_record_set_field_internal(ExpandedRecordHeader *erh, int fnumber,
 								   bool check_constraints)
 {
 	TupleDesc	tupdesc;
-	Form_pg_attribute attr;
+	TupleDescDeformAttr *attr;
 	Datum	   *dvalues;
 	bool	   *dnulls;
 	char	   *oldValue;
@@ -1146,8 +1146,8 @@ expanded_record_set_field_internal(ExpandedRecordHeader *erh, int fnumber,
 	 * Copy new field value into record's context, and deal with detoasting,
 	 * if needed.
 	 */
-	attr = TupleDescAttr(tupdesc, fnumber - 1);
-	if (!isnull && !attr->attbyval)
+	attr = TupleDescDeformAttr(tupdesc, fnumber - 1);
+	if (!isnull && !DeformAttrByVal(attr))
 	{
 		MemoryContext oldcxt;
 
@@ -1201,7 +1201,7 @@ expanded_record_set_field_internal(ExpandedRecordHeader *erh, int fnumber,
 	erh->flat_size = 0;
 
 	/* Grab old field value for pfree'ing, if needed. */
-	if (!attr->attbyval && !dnulls[fnumber - 1])
+	if (!DeformAttrByVal(attr) && !dnulls[fnumber - 1])
 		oldValue = (char *) DatumGetPointer(dvalues[fnumber - 1]);
 	else
 		oldValue = NULL;
@@ -1279,18 +1279,18 @@ expanded_record_set_fields(ExpandedRecordHeader *erh,
 
 	for (fnumber = 0; fnumber < erh->nfields; fnumber++)
 	{
-		Form_pg_attribute attr = TupleDescAttr(tupdesc, fnumber);
+		TupleDescDeformAttr *attr = TupleDescDeformAttr(tupdesc, fnumber);
 		Datum		newValue;
 		bool		isnull;
 
 		/* Ignore dropped columns */
-		if (attr->attisdropped)
+		if (DeformAttrIsDropped(attr))
 			continue;
 
 		newValue = newValues[fnumber];
 		isnull = isnulls[fnumber];
 
-		if (!attr->attbyval)
+		if (!DeformAttrByVal(attr))
 		{
 			/*
 			 * Copy new field value into record's context, and deal with
@@ -1541,9 +1541,9 @@ check_domain_for_new_field(ExpandedRecordHeader *erh, int fnumber,
 	 */
 	if (!isnull)
 	{
-		Form_pg_attribute attr = TupleDescAttr(erh->er_tupdesc, fnumber - 1);
+		TupleDescDeformAttr *attr = TupleDescDeformAttr(erh->er_tupdesc, fnumber - 1);
 
-		if (!attr->attbyval && attr->attlen == -1 &&
+		if (!DeformAttrByVal(attr) && attr->attlen == -1 &&
 			VARATT_IS_EXTERNAL(DatumGetPointer(newValue)))
 			dummy_erh->flags |= ER_FLAG_HAVE_EXTERNAL;
 	}
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index 62601a6d80..f4637e466a 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -2833,9 +2833,9 @@ ri_KeysEqual(Relation rel, TupleTableSlot *oldslot, TupleTableSlot *newslot,
 			 * difference for ON UPDATE CASCADE, but for consistency we treat
 			 * all changes to the PK the same.
 			 */
-			Form_pg_attribute att = TupleDescAttr(oldslot->tts_tupleDescriptor, attnums[i] - 1);
+			TupleDescDeformAttr *att = TupleDescDeformAttr(oldslot->tts_tupleDescriptor, attnums[i] - 1);
 
-			if (!datum_image_eq(oldvalue, newvalue, att->attbyval, att->attlen))
+			if (!datum_image_eq(oldvalue, newvalue, DeformAttrByVal(att), att->attlen))
 				return false;
 		}
 		else
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 930cc03ee2..0b9a97d174 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -586,6 +586,10 @@ RelationBuildTupleDesc(Relation relation)
 			   attp,
 			   ATTRIBUTE_FIXED_PART_SIZE);
 
+		populate_TupleDescAttr(TupleDescDeformAttr(relation->rd_att,
+												   attnum - 1),
+							   attp);
+
 		/* Update constraint/default info */
 		if (attp->attnotnull)
 			constr->has_not_null = true;
@@ -1967,6 +1971,9 @@ formrdesc(const char *relationName, Oid relationReltype,
 		has_not_null |= attrs[i].attnotnull;
 		/* make sure attcacheoff is valid */
 		TupleDescAttr(relation->rd_att, i)->attcacheoff = -1;
+
+		populate_TupleDescAttr(TupleDescDeformAttr(relation->rd_att, i),
+							   TupleDescAttr(relation->rd_att, i));
 	}
 
 	/* initialize first attribute's attcacheoff, cf RelationBuildTupleDesc */
@@ -4436,6 +4443,9 @@ BuildHardcodedDescriptor(int natts, const FormData_pg_attribute *attrs)
 		memcpy(TupleDescAttr(result, i), &attrs[i], ATTRIBUTE_FIXED_PART_SIZE);
 		/* make sure attcacheoff is valid */
 		TupleDescAttr(result, i)->attcacheoff = -1;
+
+		populate_TupleDescAttr(TupleDescDeformAttr(result, i),
+							   TupleDescAttr(result, i));
 	}
 
 	/* initialize first attribute's attcacheoff, cf RelationBuildTupleDesc */
@@ -6174,6 +6184,8 @@ load_relcache_init_file(bool shared)
 				goto read_failed;
 
 			has_not_null |= attr->attnotnull;
+
+			populate_TupleDescAttr(TupleDescDeformAttr(rel->rd_att, i), attr);
 		}
 
 		/* next read the access method specific field */
diff --git a/src/include/access/htup_details.h b/src/include/access/htup_details.h
index 5e38ef8696..92f5882b27 100644
--- a/src/include/access/htup_details.h
+++ b/src/include/access/htup_details.h
@@ -758,12 +758,12 @@ fastgetattr(HeapTuple tup, int attnum, TupleDesc tupleDesc, bool *isnull)
 	*isnull = false;
 	if (HeapTupleNoNulls(tup))
 	{
-		Form_pg_attribute att;
+		TupleDescDeformAttr *att;
 
-		att = TupleDescAttr(tupleDesc, attnum - 1);
+		att = TupleDescDeformAttr(tupleDesc, attnum - 1);
 		if (att->attcacheoff >= 0)
-			return fetchatt(att, (char *) tup->t_data + tup->t_data->t_hoff +
-							att->attcacheoff);
+			return fetchatt_fast(att, (char *) tup->t_data + tup->t_data->t_hoff +
+								 att->attcacheoff);
 		else
 			return nocachegetattr(tup, attnum, tupleDesc);
 	}
diff --git a/src/include/access/itup.h b/src/include/access/itup.h
index 94885751e5..f9d33e3bb1 100644
--- a/src/include/access/itup.h
+++ b/src/include/access/itup.h
@@ -117,18 +117,21 @@ IndexInfoFindDataOffset(unsigned short t_info)
 static inline Datum
 index_getattr(IndexTuple tup, int attnum, TupleDesc tupleDesc, bool *isnull)
 {
+	TupleDescDeformAttr *attr;
+
 	Assert(PointerIsValid(isnull));
 	Assert(attnum > 0);
 
 	*isnull = false;
+	attr = TupleDescDeformAttr(tupleDesc, attnum - 1);
 
 	if (!IndexTupleHasNulls(tup))
 	{
-		if (TupleDescAttr(tupleDesc, attnum - 1)->attcacheoff >= 0)
+		if (attr->attcacheoff >= 0)
 		{
-			return fetchatt(TupleDescAttr(tupleDesc, attnum - 1),
-							(char *) tup + IndexInfoFindDataOffset(tup->t_info)
-							+ TupleDescAttr(tupleDesc, attnum - 1)->attcacheoff);
+			return fetchatt_fast(attr,
+								 (char *) tup + IndexInfoFindDataOffset(tup->t_info) +
+								 attr->attcacheoff);
 		}
 		else
 			return nocache_index_getattr(tup, attnum, tupleDesc);
diff --git a/src/include/access/tupdesc.h b/src/include/access/tupdesc.h
index 2c435cdcb2..908c32254e 100644
--- a/src/include/access/tupdesc.h
+++ b/src/include/access/tupdesc.h
@@ -45,6 +45,43 @@ typedef struct TupleConstr
 	bool		has_generated_stored;
 } TupleConstr;
 
+/*
+ * Cut-down version of FormData_pg_attribute for faster access for tuple
+ * deformation.
+ */
+typedef struct TupleDescDeformAttr
+{
+	int32	attcacheoff;
+	int16	attlen;
+	uint8	attflags;
+	char	attalign;
+} TupleDescDeformAttr;
+
+#define DEFORM_ATTR_FLAG_BYVAL			(1 << 0)
+#define DEFORM_ATTR_FLAG_IS_PACKABLE	(1 << 1)
+#define DEFORM_ATTR_FLAG_HAS_MISSING	(1 << 2)
+#define DEFORM_ATTR_FLAG_IS_DROPPED		(1 << 3)
+#define DEFORM_ATTR_FLAG_IS_GENERATED	(1 << 4)
+#define DEFORM_ATTR_FLAG_IS_NOTNULL		(1 << 5)
+
+#define DeformAttrByVal(att) \
+	(((att)->attflags & DEFORM_ATTR_FLAG_BYVAL) != 0)
+
+#define DeformAttrIsPackable(att) \
+	(((att)->attflags & DEFORM_ATTR_FLAG_IS_PACKABLE) != 0)
+
+#define DeformAttrHasMissing(att) \
+	(((att)->attflags & DEFORM_ATTR_FLAG_HAS_MISSING) != 0)
+
+#define DeformAttrIsDropped(att) \
+	(((att)->attflags & DEFORM_ATTR_FLAG_IS_DROPPED) != 0)
+
+#define DeformAttrIsGenerated(att) \
+	(((att)->attflags & DEFORM_ATTR_FLAG_IS_GENERATED) != 0)
+
+#define DeformAttrIsNotNull(att) \
+	(((att)->attflags & DEFORM_ATTR_FLAG_IS_NOTNULL) != 0)
+
 /*
  * This struct is passed around within the backend to describe the structure
  * of tuples.  For tuples coming from on-disk relations, the information is
@@ -85,12 +122,19 @@ typedef struct TupleDescData
 	TupleConstr *constr;		/* constraints, or NULL if none */
 	/* attrs[N] is the description of Attribute Number N+1 */
 	FormData_pg_attribute *attrs;
+	TupleDescDeformAttr deform_attrs[FLEXIBLE_ARRAY_MEMBER];
 }			TupleDescData;
 typedef struct TupleDescData *TupleDesc;
 
 /* Accessor for the i'th attribute of tupdesc. */
 #define TupleDescAttr(tupdesc, i) (&(tupdesc)->attrs[(i)])
 
+/* Accessor for the i'th TupleDescAttr of tupdesc */
+#define TupleDescDeformAttr(tupdesc, i) (&(tupdesc)->deform_attrs[(i)])
+
+extern void populate_TupleDescAttr(TupleDescDeformAttr *dst,
+								   Form_pg_attribute src);
+
 extern TupleDesc CreateTemplateTupleDesc(int natts);
 
 extern TupleDesc CreateTupleDesc(int natts, Form_pg_attribute *attrs);
@@ -99,13 +143,15 @@ extern TupleDesc CreateTupleDescCopy(TupleDesc tupdesc);
 
 extern TupleDesc CreateTupleDescCopyConstr(TupleDesc tupdesc);
 
-#define TupleDescSize(src) MAXALIGN(sizeof(TupleDescData))
+#define TupleDescSize(src) \
+	(offsetof(struct TupleDescData, deform_attrs) + \
+	 (src)->natts * sizeof(TupleDescDeformAttr))
 
 #define TupleDescFullSize(src) \
-	(MAXALIGN(sizeof(TupleDescData)) + sizeof(FormData_pg_attribute) * (src)->natts)
+	(TupleDescSize(src) + sizeof(FormData_pg_attribute) * (src)->natts)
 
 #define TupleDescAttrAddress(desc) \
-	(Form_pg_attribute) ((char *) (desc) + MAXALIGN(sizeof(TupleDescData)))
+	(Form_pg_attribute) ((char *) (desc) + TupleDescSize(desc))
 
 extern void TupleDescCopy(TupleDesc dst, TupleDesc src);
 
diff --git a/src/include/access/tupmacs.h b/src/include/access/tupmacs.h
index 58b3a58cfd..f96820f64a 100644
--- a/src/include/access/tupmacs.h
+++ b/src/include/access/tupmacs.h
@@ -45,6 +45,8 @@ att_isnull(int ATT, const bits8 *BITS)
  */
 #define fetchatt(A,T) fetch_att(T, (A)->attbyval, (A)->attlen)
 
+#define fetchatt_fast(A, T) fetch_att(T, DeformAttrByVal(A), (A)->attlen)
+
 /*
  * Same, but work from byval/len parameters rather than Form_pg_attribute.
  */
-- 
2.34.1