diff --git a/contrib/dblink/dblink.c b/contrib/dblink/dblink.c
index 81136b131c992a54039f8b61cf2dc2938e99e228..3113b07ab8e8be25fb3c4320d6a6c3cfe821dc1e 100644
--- a/contrib/dblink/dblink.c
+++ b/contrib/dblink/dblink.c
@@ -2172,14 +2172,16 @@ get_sql_insert(Relation rel, int *pkattnums, int pknumatts, char **src_pkattvals
 	needComma = false;
 	for (i = 0; i < natts; i++)
 	{
-		if (tupdesc->attrs[i]->attisdropped)
+		Form_pg_attribute att = TupleDescAttr(tupdesc, i);
+
+		if (att->attisdropped)
 			continue;
 
 		if (needComma)
 			appendStringInfoChar(&buf, ',');
 
 		appendStringInfoString(&buf,
-							   quote_ident_cstr(NameStr(tupdesc->attrs[i]->attname)));
+							   quote_ident_cstr(NameStr(att->attname)));
 		needComma = true;
 	}
 
@@ -2191,7 +2193,7 @@ get_sql_insert(Relation rel, int *pkattnums, int pknumatts, char **src_pkattvals
 	needComma = false;
 	for (i = 0; i < natts; i++)
 	{
-		if (tupdesc->attrs[i]->attisdropped)
+		if (TupleDescAttr(tupdesc, i)->attisdropped)
 			continue;
 
 		if (needComma)
@@ -2237,12 +2239,13 @@ get_sql_delete(Relation rel, int *pkattnums, int pknumatts, char **tgt_pkattvals
 	for (i = 0; i < pknumatts; i++)
 	{
 		int			pkattnum = pkattnums[i];
+		Form_pg_attribute attr = TupleDescAttr(tupdesc, pkattnum);
 
 		if (i > 0)
 			appendStringInfoString(&buf, " AND ");
 
 		appendStringInfoString(&buf,
-							   quote_ident_cstr(NameStr(tupdesc->attrs[pkattnum]->attname)));
+							   quote_ident_cstr(NameStr(attr->attname)));
 
 		if (tgt_pkattvals[i] != NULL)
 			appendStringInfo(&buf, " = %s",
@@ -2289,14 +2292,16 @@ get_sql_update(Relation rel, int *pkattnums, int pknumatts, char **src_pkattvals
 	needComma = false;
 	for (i = 0; i < natts; i++)
 	{
-		if (tupdesc->attrs[i]->attisdropped)
+		Form_pg_attribute attr = TupleDescAttr(tupdesc, i);
+
+		if (attr->attisdropped)
 			continue;
 
 		if (needComma)
 			appendStringInfoString(&buf, ", ");
 
 		appendStringInfo(&buf, "%s = ",
-						 quote_ident_cstr(NameStr(tupdesc->attrs[i]->attname)));
+						 quote_ident_cstr(NameStr(attr->attname)));
 
 		key = get_attnum_pk_pos(pkattnums, pknumatts, i);
 
@@ -2320,12 +2325,13 @@ get_sql_update(Relation rel, int *pkattnums, int pknumatts, char **src_pkattvals
 	for (i = 0; i < pknumatts; i++)
 	{
 		int			pkattnum = pkattnums[i];
+		Form_pg_attribute attr = TupleDescAttr(tupdesc, pkattnum);
 
 		if (i > 0)
 			appendStringInfoString(&buf, " AND ");
 
 		appendStringInfoString(&buf,
-							   quote_ident_cstr(NameStr(tupdesc->attrs[pkattnum]->attname)));
+							   quote_ident_cstr(NameStr(attr->attname)));
 
 		val = tgt_pkattvals[i];
 
@@ -2409,14 +2415,16 @@ get_tuple_of_interest(Relation rel, int *pkattnums, int pknumatts, char **src_pk
 
 	for (i = 0; i < natts; i++)
 	{
+		Form_pg_attribute attr = TupleDescAttr(tupdesc, i);
+
 		if (i > 0)
 			appendStringInfoString(&buf, ", ");
 
-		if (tupdesc->attrs[i]->attisdropped)
+		if (attr->attisdropped)
 			appendStringInfoString(&buf, "NULL");
 		else
 			appendStringInfoString(&buf,
-								   quote_ident_cstr(NameStr(tupdesc->attrs[i]->attname)));
+								   quote_ident_cstr(NameStr(attr->attname)));
 	}
 
 	appendStringInfo(&buf, " FROM %s WHERE ", relname);
@@ -2424,12 +2432,13 @@ get_tuple_of_interest(Relation rel, int *pkattnums, int pknumatts, char **src_pk
 	for (i = 0; i < pknumatts; i++)
 	{
 		int			pkattnum = pkattnums[i];
+		Form_pg_attribute attr = TupleDescAttr(tupdesc, pkattnum);
 
 		if (i > 0)
 			appendStringInfoString(&buf, " AND ");
 
 		appendStringInfoString(&buf,
-							   quote_ident_cstr(NameStr(tupdesc->attrs[pkattnum]->attname)));
+							   quote_ident_cstr(NameStr(attr->attname)));
 
 		if (src_pkattvals[i] != NULL)
 			appendStringInfo(&buf, " = %s",
@@ -2894,7 +2903,7 @@ validate_pkattnums(Relation rel,
 		for (j = 0; j < natts; j++)
 		{
 			/* dropped columns don't count */
-			if (tupdesc->attrs[j]->attisdropped)
+			if (TupleDescAttr(tupdesc, j)->attisdropped)
 				continue;
 
 			if (++lnum == pkattnum)
diff --git a/contrib/file_fdw/file_fdw.c b/contrib/file_fdw/file_fdw.c
index 2396bd442fa4f61048fa35e129cfd8856a62a7f7..94e50e92f7b7827e3b078b3219a18f9aa0599a1d 100644
--- a/contrib/file_fdw/file_fdw.c
+++ b/contrib/file_fdw/file_fdw.c
@@ -430,7 +430,7 @@ get_file_fdw_attribute_options(Oid relid)
 	/* Retrieve FDW options for all user-defined attributes. */
 	for (attnum = 1; attnum <= natts; attnum++)
 	{
-		Form_pg_attribute attr = tupleDesc->attrs[attnum - 1];
+		Form_pg_attribute attr = TupleDescAttr(tupleDesc, attnum - 1);
 		List	   *options;
 		ListCell   *lc;
 
@@ -898,7 +898,7 @@ check_selective_binary_conversion(RelOptInfo *baserel,
 		/* Get user attributes. */
 		if (attnum > 0)
 		{
-			Form_pg_attribute attr = tupleDesc->attrs[attnum - 1];
+			Form_pg_attribute attr = TupleDescAttr(tupleDesc, attnum - 1);
 			char	   *attname = NameStr(attr->attname);
 
 			/* Skip dropped attributes (probably shouldn't see any here). */
@@ -912,7 +912,7 @@ check_selective_binary_conversion(RelOptInfo *baserel,
 	numattrs = 0;
 	for (i = 0; i < tupleDesc->natts; i++)
 	{
-		Form_pg_attribute attr = tupleDesc->attrs[i];
+		Form_pg_attribute attr = TupleDescAttr(tupleDesc, i);
 
 		if (attr->attisdropped)
 			continue;
diff --git a/contrib/hstore/hstore_io.c b/contrib/hstore/hstore_io.c
index e03005c9232b1244738c21e17f05224472820919..a44c1b223530694e8a6b0bc21c83665ce2840657 100644
--- a/contrib/hstore/hstore_io.c
+++ b/contrib/hstore/hstore_io.c
@@ -855,15 +855,16 @@ hstore_from_record(PG_FUNCTION_ARGS)
 	for (i = 0, j = 0; i < ncolumns; ++i)
 	{
 		ColumnIOData *column_info = &my_extra->columns[i];
-		Oid			column_type = tupdesc->attrs[i]->atttypid;
+		Form_pg_attribute att = TupleDescAttr(tupdesc, i);
+		Oid			column_type = att->atttypid;
 		char	   *value;
 
 		/* Ignore dropped columns in datatype */
-		if (tupdesc->attrs[i]->attisdropped)
+		if (att->attisdropped)
 			continue;
 
-		pairs[j].key = NameStr(tupdesc->attrs[i]->attname);
-		pairs[j].keylen = hstoreCheckKeyLen(strlen(NameStr(tupdesc->attrs[i]->attname)));
+		pairs[j].key = NameStr(att->attname);
+		pairs[j].keylen = hstoreCheckKeyLen(strlen(NameStr(att->attname)));
 
 		if (!nulls || nulls[i])
 		{
@@ -1034,21 +1035,22 @@ hstore_populate_record(PG_FUNCTION_ARGS)
 	for (i = 0; i < ncolumns; ++i)
 	{
 		ColumnIOData *column_info = &my_extra->columns[i];
-		Oid			column_type = tupdesc->attrs[i]->atttypid;
+		Form_pg_attribute att = TupleDescAttr(tupdesc, i);
+		Oid			column_type = att->atttypid;
 		char	   *value;
 		int			idx;
 		int			vallen;
 
 		/* Ignore dropped columns in datatype */
-		if (tupdesc->attrs[i]->attisdropped)
+		if (att->attisdropped)
 		{
 			nulls[i] = true;
 			continue;
 		}
 
 		idx = hstoreFindKey(hs, 0,
-							NameStr(tupdesc->attrs[i]->attname),
-							strlen(NameStr(tupdesc->attrs[i]->attname)));
+							NameStr(att->attname),
+							strlen(NameStr(att->attname)));
 
 		/*
 		 * we can't just skip here if the key wasn't found since we might have
@@ -1082,7 +1084,7 @@ hstore_populate_record(PG_FUNCTION_ARGS)
 			 */
 			values[i] = InputFunctionCall(&column_info->proc, NULL,
 										  column_info->typioparam,
-										  tupdesc->attrs[i]->atttypmod);
+										  att->atttypmod);
 			nulls[i] = true;
 		}
 		else
@@ -1094,7 +1096,7 @@ hstore_populate_record(PG_FUNCTION_ARGS)
 
 			values[i] = InputFunctionCall(&column_info->proc, value,
 										  column_info->typioparam,
-										  tupdesc->attrs[i]->atttypmod);
+										  att->atttypmod);
 			nulls[i] = false;
 		}
 	}
diff --git a/contrib/pageinspect/heapfuncs.c b/contrib/pageinspect/heapfuncs.c
index 72d1776a4a50f80cbe99a65594cf70b599250842..ca4d3f530f566804070b73542248c2aa800b3e2e 100644
--- a/contrib/pageinspect/heapfuncs.c
+++ b/contrib/pageinspect/heapfuncs.c
@@ -316,7 +316,7 @@ tuple_data_split_internal(Oid relid, char *tupdata,
 		bool		is_null;
 		bytea	   *attr_data = NULL;
 
-		attr = tupdesc->attrs[i];
+		attr = TupleDescAttr(tupdesc, i);
 		is_null = (t_infomask & HEAP_HASNULL) && att_isnull(i, t_bits);
 
 		/*
@@ -334,7 +334,7 @@ tuple_data_split_internal(Oid relid, char *tupdata,
 
 			if (attr->attlen == -1)
 			{
-				off = att_align_pointer(off, tupdesc->attrs[i]->attalign, -1,
+				off = att_align_pointer(off, attr->attalign, -1,
 										tupdata + off);
 
 				/*
@@ -353,7 +353,7 @@ tuple_data_split_internal(Oid relid, char *tupdata,
 			}
 			else
 			{
-				off = att_align_nominal(off, tupdesc->attrs[i]->attalign);
+				off = att_align_nominal(off, attr->attalign);
 				len = attr->attlen;
 			}
 
@@ -371,7 +371,7 @@ tuple_data_split_internal(Oid relid, char *tupdata,
 				memcpy(VARDATA(attr_data), tupdata + off, len);
 			}
 
-			off = att_addlength_pointer(off, tupdesc->attrs[i]->attlen,
+			off = att_addlength_pointer(off, attr->attlen,
 										tupdata + off);
 		}
 
diff --git a/contrib/pageinspect/rawpage.c b/contrib/pageinspect/rawpage.c
index e9d3131bda4cad99fa1e6a95fed8d74a37268f4d..25af22f4530b0ba16e0a9df6c3c69a4a5da9b9c4 100644
--- a/contrib/pageinspect/rawpage.c
+++ b/contrib/pageinspect/rawpage.c
@@ -253,7 +253,7 @@ page_header(PG_FUNCTION_ARGS)
 	lsn = PageGetLSN(page);
 
 	/* pageinspect >= 1.2 uses pg_lsn instead of text for the LSN field. */
-	if (tupdesc->attrs[0]->atttypid == TEXTOID)
+	if (TupleDescAttr(tupdesc, 0)->atttypid == TEXTOID)
 	{
 		char		lsnchar[64];
 
diff --git a/contrib/postgres_fdw/deparse.c b/contrib/postgres_fdw/deparse.c
index 285cf1b2eeab87588992f5372a9083218ab64aff..0876589fe54584f142b0b6289b5d19c5f084396b 100644
--- a/contrib/postgres_fdw/deparse.c
+++ b/contrib/postgres_fdw/deparse.c
@@ -1115,7 +1115,7 @@ deparseTargetList(StringInfo buf,
 	first = true;
 	for (i = 1; i <= tupdesc->natts; i++)
 	{
-		Form_pg_attribute attr = tupdesc->attrs[i - 1];
+		Form_pg_attribute attr = TupleDescAttr(tupdesc, i - 1);
 
 		/* Ignore dropped attributes. */
 		if (attr->attisdropped)
@@ -1851,7 +1851,7 @@ deparseAnalyzeSql(StringInfo buf, Relation rel, List **retrieved_attrs)
 	for (i = 0; i < tupdesc->natts; i++)
 	{
 		/* Ignore dropped columns. */
-		if (tupdesc->attrs[i]->attisdropped)
+		if (TupleDescAttr(tupdesc, i)->attisdropped)
 			continue;
 
 		if (!first)
@@ -1859,7 +1859,7 @@ deparseAnalyzeSql(StringInfo buf, Relation rel, List **retrieved_attrs)
 		first = false;
 
 		/* Use attribute name or column_name option. */
-		colname = NameStr(tupdesc->attrs[i]->attname);
+		colname = NameStr(TupleDescAttr(tupdesc, i)->attname);
 		options = GetForeignColumnOptions(relid, i + 1);
 
 		foreach(lc, options)
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index a30afca1d69c77d90bb29c9fcd09989d4d66e634..32dc4e6301f8080cc3f5896e09f9132ed1dd1058 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -1575,7 +1575,7 @@ postgresPlanForeignModify(PlannerInfo *root,
 
 		for (attnum = 1; attnum <= tupdesc->natts; attnum++)
 		{
-			Form_pg_attribute attr = tupdesc->attrs[attnum - 1];
+			Form_pg_attribute attr = TupleDescAttr(tupdesc, attnum - 1);
 
 			if (!attr->attisdropped)
 				targetAttrs = lappend_int(targetAttrs, attnum);
@@ -1675,6 +1675,7 @@ postgresBeginForeignModify(ModifyTableState *mtstate,
 	Oid			typefnoid;
 	bool		isvarlena;
 	ListCell   *lc;
+	TupleDesc	tupdesc = RelationGetDescr(rel);
 
 	/*
 	 * Do nothing in EXPLAIN (no ANALYZE) case.  resultRelInfo->ri_FdwState
@@ -1719,7 +1720,7 @@ postgresBeginForeignModify(ModifyTableState *mtstate,
 
 	/* Prepare for input conversion of RETURNING results. */
 	if (fmstate->has_returning)
-		fmstate->attinmeta = TupleDescGetAttInMetadata(RelationGetDescr(rel));
+		fmstate->attinmeta = TupleDescGetAttInMetadata(tupdesc);
 
 	/* Prepare for output conversion of parameters used in prepared stmt. */
 	n_params = list_length(fmstate->target_attrs) + 1;
@@ -1748,7 +1749,7 @@ postgresBeginForeignModify(ModifyTableState *mtstate,
 		foreach(lc, fmstate->target_attrs)
 		{
 			int			attnum = lfirst_int(lc);
-			Form_pg_attribute attr = RelationGetDescr(rel)->attrs[attnum - 1];
+			Form_pg_attribute attr = TupleDescAttr(tupdesc, attnum - 1);
 
 			Assert(!attr->attisdropped);
 
@@ -5090,9 +5091,10 @@ conversion_error_callback(void *arg)
 	{
 		/* error occurred in a scan against a foreign table */
 		TupleDesc	tupdesc = RelationGetDescr(errpos->rel);
+		Form_pg_attribute attr = TupleDescAttr(tupdesc, errpos->cur_attno - 1);
 
 		if (errpos->cur_attno > 0 && errpos->cur_attno <= tupdesc->natts)
-			attname = NameStr(tupdesc->attrs[errpos->cur_attno - 1]->attname);
+			attname = NameStr(attr->attname);
 		else if (errpos->cur_attno == SelfItemPointerAttributeNumber)
 			attname = "ctid";
 		else if (errpos->cur_attno == ObjectIdAttributeNumber)
diff --git a/contrib/spi/timetravel.c b/contrib/spi/timetravel.c
index f7905e20dbaa3a81b3904395fd6075f562d3b347..2c66d888dfe1132adad55a8784c3bbfbb17e7b03 100644
--- a/contrib/spi/timetravel.c
+++ b/contrib/spi/timetravel.c
@@ -328,7 +328,7 @@ timetravel(PG_FUNCTION_ARGS)
 		for (i = 1; i <= natts; i++)
 		{
 			ctypes[i - 1] = SPI_gettypeid(tupdesc, i);
-			if (!(tupdesc->attrs[i - 1]->attisdropped)) /* skip dropped columns */
+			if (!(TupleDescAttr(tupdesc, i - 1)->attisdropped)) /* skip dropped columns */
 			{
 				snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), "%c$%d", separ, i);
 				separ = ',';
diff --git a/contrib/tablefunc/tablefunc.c b/contrib/tablefunc/tablefunc.c
index 0bc8177b61b570774920c31fc8e03c23e71affd3..7369c713515362def3233de2e89d5974656e68c9 100644
--- a/contrib/tablefunc/tablefunc.c
+++ b/contrib/tablefunc/tablefunc.c
@@ -1421,7 +1421,7 @@ build_tuplestore_recursively(char *key_fld,
  * Check expected (query runtime) tupdesc suitable for Connectby
  */
 static void
-validateConnectbyTupleDesc(TupleDesc tupdesc, bool show_branch, bool show_serial)
+validateConnectbyTupleDesc(TupleDesc td, bool show_branch, bool show_serial)
 {
 	int			serial_column = 0;
 
@@ -1431,7 +1431,7 @@ validateConnectbyTupleDesc(TupleDesc tupdesc, bool show_branch, bool show_serial
 	/* are there the correct number of columns */
 	if (show_branch)
 	{
-		if (tupdesc->natts != (CONNECTBY_NCOLS + serial_column))
+		if (td->natts != (CONNECTBY_NCOLS + serial_column))
 			ereport(ERROR,
 					(errcode(ERRCODE_DATATYPE_MISMATCH),
 					 errmsg("invalid return type"),
@@ -1440,7 +1440,7 @@ validateConnectbyTupleDesc(TupleDesc tupdesc, bool show_branch, bool show_serial
 	}
 	else
 	{
-		if (tupdesc->natts != CONNECTBY_NCOLS_NOBRANCH + serial_column)
+		if (td->natts != CONNECTBY_NCOLS_NOBRANCH + serial_column)
 			ereport(ERROR,
 					(errcode(ERRCODE_DATATYPE_MISMATCH),
 					 errmsg("invalid return type"),
@@ -1449,14 +1449,14 @@ validateConnectbyTupleDesc(TupleDesc tupdesc, bool show_branch, bool show_serial
 	}
 
 	/* check that the types of the first two columns match */
-	if (tupdesc->attrs[0]->atttypid != tupdesc->attrs[1]->atttypid)
+	if (TupleDescAttr(td, 0)->atttypid != TupleDescAttr(td, 1)->atttypid)
 		ereport(ERROR,
 				(errcode(ERRCODE_DATATYPE_MISMATCH),
 				 errmsg("invalid return type"),
 				 errdetail("First two columns must be the same type.")));
 
 	/* check that the type of the third column is INT4 */
-	if (tupdesc->attrs[2]->atttypid != INT4OID)
+	if (TupleDescAttr(td, 2)->atttypid != INT4OID)
 		ereport(ERROR,
 				(errcode(ERRCODE_DATATYPE_MISMATCH),
 				 errmsg("invalid return type"),
@@ -1464,7 +1464,7 @@ validateConnectbyTupleDesc(TupleDesc tupdesc, bool show_branch, bool show_serial
 						   format_type_be(INT4OID))));
 
 	/* check that the type of the fourth column is TEXT if applicable */
-	if (show_branch && tupdesc->attrs[3]->atttypid != TEXTOID)
+	if (show_branch && TupleDescAttr(td, 3)->atttypid != TEXTOID)
 		ereport(ERROR,
 				(errcode(ERRCODE_DATATYPE_MISMATCH),
 				 errmsg("invalid return type"),
@@ -1472,7 +1472,8 @@ validateConnectbyTupleDesc(TupleDesc tupdesc, bool show_branch, bool show_serial
 						   format_type_be(TEXTOID))));
 
 	/* check that the type of the fifth column is INT4 */
-	if (show_branch && show_serial && tupdesc->attrs[4]->atttypid != INT4OID)
+	if (show_branch && show_serial &&
+		TupleDescAttr(td, 4)->atttypid != INT4OID)
 		ereport(ERROR,
 				(errcode(ERRCODE_DATATYPE_MISMATCH),
 				 errmsg("query-specified return tuple not valid for Connectby: "
@@ -1480,7 +1481,8 @@ validateConnectbyTupleDesc(TupleDesc tupdesc, bool show_branch, bool show_serial
 						format_type_be(INT4OID))));
 
 	/* check that the type of the fifth column is INT4 */
-	if (!show_branch && show_serial && tupdesc->attrs[3]->atttypid != INT4OID)
+	if (!show_branch && show_serial &&
+		TupleDescAttr(td, 3)->atttypid != INT4OID)
 		ereport(ERROR,
 				(errcode(ERRCODE_DATATYPE_MISMATCH),
 				 errmsg("query-specified return tuple not valid for Connectby: "
@@ -1514,10 +1516,10 @@ compatConnectbyTupleDescs(TupleDesc ret_tupdesc, TupleDesc sql_tupdesc)
 	 * These columns must match the result type indicated by the calling
 	 * query.
 	 */
-	ret_atttypid = ret_tupdesc->attrs[0]->atttypid;
-	sql_atttypid = sql_tupdesc->attrs[0]->atttypid;
-	ret_atttypmod = ret_tupdesc->attrs[0]->atttypmod;
-	sql_atttypmod = sql_tupdesc->attrs[0]->atttypmod;
+	ret_atttypid = TupleDescAttr(ret_tupdesc, 0)->atttypid;
+	sql_atttypid = TupleDescAttr(sql_tupdesc, 0)->atttypid;
+	ret_atttypmod = TupleDescAttr(ret_tupdesc, 0)->atttypmod;
+	sql_atttypmod = TupleDescAttr(sql_tupdesc, 0)->atttypmod;
 	if (ret_atttypid != sql_atttypid ||
 		(ret_atttypmod >= 0 && ret_atttypmod != sql_atttypmod))
 		ereport(ERROR,
@@ -1528,10 +1530,10 @@ compatConnectbyTupleDescs(TupleDesc ret_tupdesc, TupleDesc sql_tupdesc)
 						   format_type_with_typemod(ret_atttypid, ret_atttypmod),
 						   format_type_with_typemod(sql_atttypid, sql_atttypmod))));
 
-	ret_atttypid = ret_tupdesc->attrs[1]->atttypid;
-	sql_atttypid = sql_tupdesc->attrs[1]->atttypid;
-	ret_atttypmod = ret_tupdesc->attrs[1]->atttypmod;
-	sql_atttypmod = sql_tupdesc->attrs[1]->atttypmod;
+	ret_atttypid = TupleDescAttr(ret_tupdesc, 1)->atttypid;
+	sql_atttypid = TupleDescAttr(sql_tupdesc, 1)->atttypid;
+	ret_atttypmod = TupleDescAttr(ret_tupdesc, 1)->atttypmod;
+	sql_atttypmod = TupleDescAttr(sql_tupdesc, 1)->atttypmod;
 	if (ret_atttypid != sql_atttypid ||
 		(ret_atttypmod >= 0 && ret_atttypmod != sql_atttypmod))
 		ereport(ERROR,
@@ -1562,8 +1564,8 @@ compatCrosstabTupleDescs(TupleDesc ret_tupdesc, TupleDesc sql_tupdesc)
 		return false;
 
 	/* check the rowid types match */
-	ret_atttypid = ret_tupdesc->attrs[0]->atttypid;
-	sql_atttypid = sql_tupdesc->attrs[0]->atttypid;
+	ret_atttypid = TupleDescAttr(ret_tupdesc, 0)->atttypid;
+	sql_atttypid = TupleDescAttr(sql_tupdesc, 0)->atttypid;
 	if (ret_atttypid != sql_atttypid)
 		ereport(ERROR,
 				(errcode(ERRCODE_DATATYPE_MISMATCH),
@@ -1576,10 +1578,10 @@ compatCrosstabTupleDescs(TupleDesc ret_tupdesc, TupleDesc sql_tupdesc)
 	 * attribute [2] of the sql tuple should match attributes [1] to [natts]
 	 * of the return tuple
 	 */
-	sql_attr = sql_tupdesc->attrs[2];
+	sql_attr = TupleDescAttr(sql_tupdesc, 2);
 	for (i = 1; i < ret_tupdesc->natts; i++)
 	{
-		ret_attr = ret_tupdesc->attrs[i];
+		ret_attr = TupleDescAttr(ret_tupdesc, i);
 
 		if (ret_attr->atttypid != sql_attr->atttypid)
 			return false;
diff --git a/contrib/tcn/tcn.c b/contrib/tcn/tcn.c
index 0b9acbf84880d112759bc116b6d5f8f4bae0db15..88674901bb33aca9575b834a831bf8c44576fdff 100644
--- a/contrib/tcn/tcn.c
+++ b/contrib/tcn/tcn.c
@@ -153,9 +153,10 @@ triggered_change_notification(PG_FUNCTION_ARGS)
 				for (i = 0; i < numatts; i++)
 				{
 					int			colno = index->indkey.values[i];
+					Form_pg_attribute attr = TupleDescAttr(tupdesc, colno - 1);
 
 					appendStringInfoCharMacro(payload, ',');
-					strcpy_quoted(payload, NameStr((tupdesc->attrs[colno - 1])->attname), '"');
+					strcpy_quoted(payload, NameStr(attr->attname), '"');
 					appendStringInfoCharMacro(payload, '=');
 					strcpy_quoted(payload, SPI_getvalue(trigtuple, tupdesc, colno), '\'');
 				}
diff --git a/contrib/test_decoding/test_decoding.c b/contrib/test_decoding/test_decoding.c
index a1a7c2ae0cc657054126a35d160f5eb88f6d0b6d..135b3b763882b546e177b8a42e06f342a0b8ae69 100644
--- a/contrib/test_decoding/test_decoding.c
+++ b/contrib/test_decoding/test_decoding.c
@@ -330,7 +330,7 @@ tuple_to_stringinfo(StringInfo s, TupleDesc tupdesc, HeapTuple tuple, bool skip_
 		Datum		origval;	/* possibly toasted Datum */
 		bool		isnull;		/* column is null? */
 
-		attr = tupdesc->attrs[natt];
+		attr = TupleDescAttr(tupdesc, natt);
 
 		/*
 		 * don't print dropped columns, we can't be sure everything is
diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index efebeb035ac15bf2775400a9290c28d254bb97b7..b3aa6d1cedc525369bea9ddf1b075d73cf832093 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -473,7 +473,8 @@ bringetbitmap(IndexScanDesc scan, TIDBitmap *tbm)
 					 */
 					Assert((key->sk_flags & SK_ISNULL) ||
 						   (key->sk_collation ==
-							bdesc->bd_tupdesc->attrs[keyattno - 1]->attcollation));
+							TupleDescAttr(bdesc->bd_tupdesc,
+										  keyattno - 1)->attcollation));
 
 					/* First time this column? look up consistent function */
 					if (consistentFn[keyattno - 1].fn_oid == InvalidOid)
@@ -622,6 +623,7 @@ brinbuildCallback(Relation index,
 	{
 		FmgrInfo   *addValue;
 		BrinValues *col;
+		Form_pg_attribute attr = TupleDescAttr(state->bs_bdesc->bd_tupdesc, i);
 
 		col = &state->bs_dtuple->bt_columns[i];
 		addValue = index_getprocinfo(index, i + 1,
@@ -631,7 +633,7 @@ brinbuildCallback(Relation index,
 		 * Update dtuple state, if and as necessary.
 		 */
 		FunctionCall4Coll(addValue,
-						  state->bs_bdesc->bd_tupdesc->attrs[i]->attcollation,
+						  attr->attcollation,
 						  PointerGetDatum(state->bs_bdesc),
 						  PointerGetDatum(col),
 						  values[i], isnull[i]);
@@ -1019,12 +1021,12 @@ brin_build_desc(Relation rel)
 	for (keyno = 0; keyno < tupdesc->natts; keyno++)
 	{
 		FmgrInfo   *opcInfoFn;
+		Form_pg_attribute attr = TupleDescAttr(tupdesc, keyno);
 
 		opcInfoFn = index_getprocinfo(rel, keyno + 1, BRIN_PROCNUM_OPCINFO);
 
 		opcinfo[keyno] = (BrinOpcInfo *)
-			DatumGetPointer(FunctionCall1(opcInfoFn,
-										  tupdesc->attrs[keyno]->atttypid));
+			DatumGetPointer(FunctionCall1(opcInfoFn, attr->atttypid));
 		totalstored += opcinfo[keyno]->oi_nstored;
 	}
 
diff --git a/src/backend/access/brin/brin_inclusion.c b/src/backend/access/brin/brin_inclusion.c
index 9c0a058ccb087f195fb6ccfe06c54edc9df93486..449ce5ea4cfe86d86edcf6598c237ffa43537eba 100644
--- a/src/backend/access/brin/brin_inclusion.c
+++ b/src/backend/access/brin/brin_inclusion.c
@@ -157,7 +157,7 @@ brin_inclusion_add_value(PG_FUNCTION_ARGS)
 	}
 
 	attno = column->bv_attno;
-	attr = bdesc->bd_tupdesc->attrs[attno - 1];
+	attr = TupleDescAttr(bdesc->bd_tupdesc, attno - 1);
 
 	/*
 	 * If the recorded value is null, copy the new value (which we know to be
@@ -516,7 +516,7 @@ brin_inclusion_union(PG_FUNCTION_ARGS)
 		PG_RETURN_VOID();
 
 	attno = col_a->bv_attno;
-	attr = bdesc->bd_tupdesc->attrs[attno - 1];
+	attr = TupleDescAttr(bdesc->bd_tupdesc, attno - 1);
 
 	/*
 	 * Adjust "allnulls".  If A doesn't have values, just copy the values from
@@ -675,7 +675,7 @@ inclusion_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, Oid subtype,
 		bool		isNull;
 
 		opfamily = bdesc->bd_index->rd_opfamily[attno - 1];
-		attr = bdesc->bd_tupdesc->attrs[attno - 1];
+		attr = TupleDescAttr(bdesc->bd_tupdesc, attno - 1);
 		tuple = SearchSysCache4(AMOPSTRATEGY, ObjectIdGetDatum(opfamily),
 								ObjectIdGetDatum(attr->atttypid),
 								ObjectIdGetDatum(subtype),
diff --git a/src/backend/access/brin/brin_minmax.c b/src/backend/access/brin/brin_minmax.c
index 62fd90aabe7ec5e671e39a4a9637feccb5405f0b..ce503972f8941c33db9faa37196e700185886116 100644
--- a/src/backend/access/brin/brin_minmax.c
+++ b/src/backend/access/brin/brin_minmax.c
@@ -90,7 +90,7 @@ brin_minmax_add_value(PG_FUNCTION_ARGS)
 	}
 
 	attno = column->bv_attno;
-	attr = bdesc->bd_tupdesc->attrs[attno - 1];
+	attr = TupleDescAttr(bdesc->bd_tupdesc, attno - 1);
 
 	/*
 	 * If the recorded value is null, store the new value (which we know to be
@@ -260,7 +260,7 @@ brin_minmax_union(PG_FUNCTION_ARGS)
 		PG_RETURN_VOID();
 
 	attno = col_a->bv_attno;
-	attr = bdesc->bd_tupdesc->attrs[attno - 1];
+	attr = TupleDescAttr(bdesc->bd_tupdesc, attno - 1);
 
 	/*
 	 * Adjust "allnulls".  If A doesn't have values, just copy the values from
@@ -347,7 +347,7 @@ minmax_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, Oid subtype,
 		bool		isNull;
 
 		opfamily = bdesc->bd_index->rd_opfamily[attno - 1];
-		attr = bdesc->bd_tupdesc->attrs[attno - 1];
+		attr = TupleDescAttr(bdesc->bd_tupdesc, attno - 1);
 		tuple = SearchSysCache4(AMOPSTRATEGY, ObjectIdGetDatum(opfamily),
 								ObjectIdGetDatum(attr->atttypid),
 								ObjectIdGetDatum(subtype),
diff --git a/src/backend/access/brin/brin_tuple.c b/src/backend/access/brin/brin_tuple.c
index ed5b4b108dac8e4d2d7d3dcf9374f13f97761b4f..5c035fb2033bb98bcdacbc981c8b08e09ede5db5 100644
--- a/src/backend/access/brin/brin_tuple.c
+++ b/src/backend/access/brin/brin_tuple.c
@@ -559,7 +559,7 @@ brin_deconstruct_tuple(BrinDesc *brdesc,
 			 datumno < brdesc->bd_info[attnum]->oi_nstored;
 			 datumno++)
 		{
-			Form_pg_attribute thisatt = diskdsc->attrs[stored];
+			Form_pg_attribute thisatt = TupleDescAttr(diskdsc, stored);
 
 			if (thisatt->attlen == -1)
 			{
diff --git a/src/backend/access/common/heaptuple.c b/src/backend/access/common/heaptuple.c
index 584a202ab51ec587b8869488265e3faea749d64b..13ee528e2616a07de177253cc9fcd13301f0dbfe 100644
--- a/src/backend/access/common/heaptuple.c
+++ b/src/backend/access/common/heaptuple.c
@@ -89,7 +89,6 @@ heap_compute_data_size(TupleDesc tupleDesc,
 	Size		data_length = 0;
 	int			i;
 	int			numberOfAttributes = tupleDesc->natts;
-	Form_pg_attribute *att = tupleDesc->attrs;
 
 	for (i = 0; i < numberOfAttributes; i++)
 	{
@@ -100,7 +99,7 @@ heap_compute_data_size(TupleDesc tupleDesc,
 			continue;
 
 		val = values[i];
-		atti = att[i];
+		atti = TupleDescAttr(tupleDesc, i);
 
 		if (ATT_IS_PACKABLE(atti) &&
 			VARATT_CAN_MAKE_SHORT(DatumGetPointer(val)))
@@ -152,7 +151,6 @@ heap_fill_tuple(TupleDesc tupleDesc,
 	int			bitmask;
 	int			i;
 	int			numberOfAttributes = tupleDesc->natts;
-	Form_pg_attribute *att = tupleDesc->attrs;
 
 #ifdef USE_ASSERT_CHECKING
 	char	   *start = data;
@@ -174,6 +172,7 @@ heap_fill_tuple(TupleDesc tupleDesc,
 
 	for (i = 0; i < numberOfAttributes; i++)
 	{
+		Form_pg_attribute att = TupleDescAttr(tupleDesc, i);
 		Size		data_length;
 
 		if (bit != NULL)
@@ -201,14 +200,14 @@ heap_fill_tuple(TupleDesc tupleDesc,
 		 * an offset.  This is a bit of a hack.
 		 */
 
-		if (att[i]->attbyval)
+		if (att->attbyval)
 		{
 			/* pass-by-value */
-			data = (char *) att_align_nominal(data, att[i]->attalign);
-			store_att_byval(data, values[i], att[i]->attlen);
-			data_length = att[i]->attlen;
+			data = (char *) att_align_nominal(data, att->attalign);
+			store_att_byval(data, values[i], att->attlen);
+			data_length = att->attlen;
 		}
-		else if (att[i]->attlen == -1)
+		else if (att->attlen == -1)
 		{
 			/* varlena */
 			Pointer		val = DatumGetPointer(values[i]);
@@ -225,7 +224,7 @@ heap_fill_tuple(TupleDesc tupleDesc,
 					ExpandedObjectHeader *eoh = DatumGetEOHP(values[i]);
 
 					data = (char *) att_align_nominal(data,
-													  att[i]->attalign);
+													  att->attalign);
 					data_length = EOH_get_flat_size(eoh);
 					EOH_flatten_into(eoh, data, data_length);
 				}
@@ -243,7 +242,7 @@ heap_fill_tuple(TupleDesc tupleDesc,
 				data_length = VARSIZE_SHORT(val);
 				memcpy(data, val, data_length);
 			}
-			else if (VARLENA_ATT_IS_PACKABLE(att[i]) &&
+			else if (VARLENA_ATT_IS_PACKABLE(att) &&
 					 VARATT_CAN_MAKE_SHORT(val))
 			{
 				/* convert to short varlena -- no alignment */
@@ -255,25 +254,25 @@ heap_fill_tuple(TupleDesc tupleDesc,
 			{
 				/* full 4-byte header varlena */
 				data = (char *) att_align_nominal(data,
-												  att[i]->attalign);
+												  att->attalign);
 				data_length = VARSIZE(val);
 				memcpy(data, val, data_length);
 			}
 		}
-		else if (att[i]->attlen == -2)
+		else if (att->attlen == -2)
 		{
 			/* cstring ... never needs alignment */
 			*infomask |= HEAP_HASVARWIDTH;
-			Assert(att[i]->attalign == 'c');
+			Assert(att->attalign == 'c');
 			data_length = strlen(DatumGetCString(values[i])) + 1;
 			memcpy(data, DatumGetPointer(values[i]), data_length);
 		}
 		else
 		{
 			/* fixed-length pass-by-reference */
-			data = (char *) att_align_nominal(data, att[i]->attalign);
-			Assert(att[i]->attlen > 0);
-			data_length = att[i]->attlen;
+			data = (char *) att_align_nominal(data, att->attalign);
+			Assert(att->attlen > 0);
+			data_length = att->attlen;
 			memcpy(data, DatumGetPointer(values[i]), data_length);
 		}
 
@@ -354,7 +353,6 @@ nocachegetattr(HeapTuple tuple,
 			   TupleDesc tupleDesc)
 {
 	HeapTupleHeader tup = tuple->t_data;
-	Form_pg_attribute *att = tupleDesc->attrs;
 	char	   *tp;				/* ptr to data part of tuple */
 	bits8	   *bp = tup->t_bits;	/* ptr to null bitmap in tuple */
 	bool		slow = false;	/* do we have to walk attrs? */
@@ -404,15 +402,15 @@ nocachegetattr(HeapTuple tuple,
 
 	if (!slow)
 	{
+		Form_pg_attribute 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.
 		 */
-		if (att[attnum]->attcacheoff >= 0)
-		{
-			return fetchatt(att[attnum],
-							tp + att[attnum]->attcacheoff);
-		}
+		att = TupleDescAttr(tupleDesc, attnum);
+		if (att->attcacheoff >= 0)
+			return fetchatt(att, tp + att->attcacheoff);
 
 		/*
 		 * Otherwise, check for non-fixed-length attrs up to and including
@@ -425,7 +423,7 @@ nocachegetattr(HeapTuple tuple,
 
 			for (j = 0; j <= attnum; j++)
 			{
-				if (att[j]->attlen <= 0)
+				if (TupleDescAttr(tupleDesc, j)->attlen <= 0)
 				{
 					slow = true;
 					break;
@@ -448,29 +446,32 @@ nocachegetattr(HeapTuple tuple,
 		 * fixed-width columns, in hope of avoiding future visits to this
 		 * routine.
 		 */
-		att[0]->attcacheoff = 0;
+		TupleDescAttr(tupleDesc, 0)->attcacheoff = 0;
 
 		/* we might have set some offsets in the slow path previously */
-		while (j < natts && att[j]->attcacheoff > 0)
+		while (j < natts && TupleDescAttr(tupleDesc, j)->attcacheoff > 0)
 			j++;
 
-		off = att[j - 1]->attcacheoff + att[j - 1]->attlen;
+		off = TupleDescAttr(tupleDesc, j - 1)->attcacheoff +
+			TupleDescAttr(tupleDesc, j - 1)->attlen;
 
 		for (; j < natts; j++)
 		{
-			if (att[j]->attlen <= 0)
+			Form_pg_attribute att = TupleDescAttr(tupleDesc, j);
+
+			if (att->attlen <= 0)
 				break;
 
-			off = att_align_nominal(off, att[j]->attalign);
+			off = att_align_nominal(off, att->attalign);
 
-			att[j]->attcacheoff = off;
+			att->attcacheoff = off;
 
-			off += att[j]->attlen;
+			off += att->attlen;
 		}
 
 		Assert(j > attnum);
 
-		off = att[attnum]->attcacheoff;
+		off = TupleDescAttr(tupleDesc, attnum)->attcacheoff;
 	}
 	else
 	{
@@ -490,6 +491,8 @@ nocachegetattr(HeapTuple tuple,
 		off = 0;
 		for (i = 0;; i++)		/* loop exit is at "break" */
 		{
+			Form_pg_attribute att = TupleDescAttr(tupleDesc, i);
+
 			if (HeapTupleHasNulls(tuple) && att_isnull(i, bp))
 			{
 				usecache = false;
@@ -497,9 +500,9 @@ nocachegetattr(HeapTuple tuple,
 			}
 
 			/* If we know the next offset, we can skip the rest */
-			if (usecache && att[i]->attcacheoff >= 0)
-				off = att[i]->attcacheoff;
-			else if (att[i]->attlen == -1)
+			if (usecache && att->attcacheoff >= 0)
+				off = att->attcacheoff;
+			else if (att->attlen == -1)
 			{
 				/*
 				 * We can only cache the offset for a varlena attribute if the
@@ -508,11 +511,11 @@ nocachegetattr(HeapTuple tuple,
 				 * either an aligned or unaligned value.
 				 */
 				if (usecache &&
-					off == att_align_nominal(off, att[i]->attalign))
-					att[i]->attcacheoff = off;
+					off == att_align_nominal(off, att->attalign))
+					att->attcacheoff = off;
 				else
 				{
-					off = att_align_pointer(off, att[i]->attalign, -1,
+					off = att_align_pointer(off, att->attalign, -1,
 											tp + off);
 					usecache = false;
 				}
@@ -520,23 +523,23 @@ nocachegetattr(HeapTuple tuple,
 			else
 			{
 				/* not varlena, so safe to use att_align_nominal */
-				off = att_align_nominal(off, att[i]->attalign);
+				off = att_align_nominal(off, att->attalign);
 
 				if (usecache)
-					att[i]->attcacheoff = off;
+					att->attcacheoff = off;
 			}
 
 			if (i == attnum)
 				break;
 
-			off = att_addlength_pointer(off, att[i]->attlen, tp + off);
+			off = att_addlength_pointer(off, att->attlen, tp + off);
 
-			if (usecache && att[i]->attlen <= 0)
+			if (usecache && att->attlen <= 0)
 				usecache = false;
 		}
 	}
 
-	return fetchatt(att[attnum], tp + off);
+	return fetchatt(TupleDescAttr(tupleDesc, attnum), tp + off);
 }
 
 /* ----------------
@@ -935,7 +938,6 @@ heap_deform_tuple(HeapTuple tuple, TupleDesc tupleDesc,
 {
 	HeapTupleHeader tup = tuple->t_data;
 	bool		hasnulls = HeapTupleHasNulls(tuple);
-	Form_pg_attribute *att = tupleDesc->attrs;
 	int			tdesc_natts = tupleDesc->natts;
 	int			natts;			/* number of atts to extract */
 	int			attnum;
@@ -959,7 +961,7 @@ heap_deform_tuple(HeapTuple tuple, TupleDesc tupleDesc,
 
 	for (attnum = 0; attnum < natts; attnum++)
 	{
-		Form_pg_attribute thisatt = att[attnum];
+		Form_pg_attribute thisatt = TupleDescAttr(tupleDesc, attnum);
 
 		if (hasnulls && att_isnull(attnum, bp))
 		{
@@ -1039,7 +1041,6 @@ slot_deform_tuple(TupleTableSlot *slot, int natts)
 	bool	   *isnull = slot->tts_isnull;
 	HeapTupleHeader tup = tuple->t_data;
 	bool		hasnulls = HeapTupleHasNulls(tuple);
-	Form_pg_attribute *att = tupleDesc->attrs;
 	int			attnum;
 	char	   *tp;				/* ptr to tuple data */
 	long		off;			/* offset in tuple data */
@@ -1068,7 +1069,7 @@ slot_deform_tuple(TupleTableSlot *slot, int natts)
 
 	for (; attnum < natts; attnum++)
 	{
-		Form_pg_attribute thisatt = att[attnum];
+		Form_pg_attribute thisatt = TupleDescAttr(tupleDesc, attnum);
 
 		if (hasnulls && att_isnull(attnum, bp))
 		{
@@ -1209,7 +1210,7 @@ slot_getattr(TupleTableSlot *slot, int attnum, bool *isnull)
 	 * This case should not happen in normal use, but it could happen if we
 	 * are executing a plan cached before the column was dropped.
 	 */
-	if (tupleDesc->attrs[attnum - 1]->attisdropped)
+	if (TupleDescAttr(tupleDesc, attnum - 1)->attisdropped)
 	{
 		*isnull = true;
 		return (Datum) 0;
diff --git a/src/backend/access/common/indextuple.c b/src/backend/access/common/indextuple.c
index 37a21057d001964ffb51e5523bb8dfce34f51ea9..138671410a2d9218fdfe0fc7f5745f23e52c2281 100644
--- a/src/backend/access/common/indextuple.c
+++ b/src/backend/access/common/indextuple.c
@@ -63,7 +63,7 @@ index_form_tuple(TupleDesc tupleDescriptor,
 #ifdef TOAST_INDEX_HACK
 	for (i = 0; i < numberOfAttributes; i++)
 	{
-		Form_pg_attribute att = tupleDescriptor->attrs[i];
+		Form_pg_attribute att = TupleDescAttr(tupleDescriptor, i);
 
 		untoasted_values[i] = values[i];
 		untoasted_free[i] = false;
@@ -209,7 +209,6 @@ nocache_index_getattr(IndexTuple tup,
 					  int attnum,
 					  TupleDesc tupleDesc)
 {
-	Form_pg_attribute *att = tupleDesc->attrs;
 	char	   *tp;				/* ptr to data part of tuple */
 	bits8	   *bp = NULL;		/* ptr to null bitmap in tuple */
 	bool		slow = false;	/* do we have to walk attrs? */
@@ -271,15 +270,15 @@ nocache_index_getattr(IndexTuple tup,
 
 	if (!slow)
 	{
+		Form_pg_attribute 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.
 		 */
-		if (att[attnum]->attcacheoff >= 0)
-		{
-			return fetchatt(att[attnum],
-							tp + att[attnum]->attcacheoff);
-		}
+		att = TupleDescAttr(tupleDesc, attnum);
+		if (att->attcacheoff >= 0)
+			return fetchatt(att, tp + att->attcacheoff);
 
 		/*
 		 * Otherwise, check for non-fixed-length attrs up to and including
@@ -292,7 +291,7 @@ nocache_index_getattr(IndexTuple tup,
 
 			for (j = 0; j <= attnum; j++)
 			{
-				if (att[j]->attlen <= 0)
+				if (TupleDescAttr(tupleDesc, j)->attlen <= 0)
 				{
 					slow = true;
 					break;
@@ -315,29 +314,32 @@ nocache_index_getattr(IndexTuple tup,
 		 * fixed-width columns, in hope of avoiding future visits to this
 		 * routine.
 		 */
-		att[0]->attcacheoff = 0;
+		TupleDescAttr(tupleDesc, 0)->attcacheoff = 0;
 
 		/* we might have set some offsets in the slow path previously */
-		while (j < natts && att[j]->attcacheoff > 0)
+		while (j < natts && TupleDescAttr(tupleDesc, j)->attcacheoff > 0)
 			j++;
 
-		off = att[j - 1]->attcacheoff + att[j - 1]->attlen;
+		off = TupleDescAttr(tupleDesc, j - 1)->attcacheoff +
+			TupleDescAttr(tupleDesc, j - 1)->attlen;
 
 		for (; j < natts; j++)
 		{
-			if (att[j]->attlen <= 0)
+			Form_pg_attribute att = TupleDescAttr(tupleDesc, j);
+
+			if (att->attlen <= 0)
 				break;
 
-			off = att_align_nominal(off, att[j]->attalign);
+			off = att_align_nominal(off, att->attalign);
 
-			att[j]->attcacheoff = off;
+			att->attcacheoff = off;
 
-			off += att[j]->attlen;
+			off += att->attlen;
 		}
 
 		Assert(j > attnum);
 
-		off = att[attnum]->attcacheoff;
+		off = TupleDescAttr(tupleDesc, attnum)->attcacheoff;
 	}
 	else
 	{
@@ -357,6 +359,8 @@ nocache_index_getattr(IndexTuple tup,
 		off = 0;
 		for (i = 0;; i++)		/* loop exit is at "break" */
 		{
+			Form_pg_attribute att = TupleDescAttr(tupleDesc, i);
+
 			if (IndexTupleHasNulls(tup) && att_isnull(i, bp))
 			{
 				usecache = false;
@@ -364,9 +368,9 @@ nocache_index_getattr(IndexTuple tup,
 			}
 
 			/* If we know the next offset, we can skip the rest */
-			if (usecache && att[i]->attcacheoff >= 0)
-				off = att[i]->attcacheoff;
-			else if (att[i]->attlen == -1)
+			if (usecache && att->attcacheoff >= 0)
+				off = att->attcacheoff;
+			else if (att->attlen == -1)
 			{
 				/*
 				 * We can only cache the offset for a varlena attribute if the
@@ -375,11 +379,11 @@ nocache_index_getattr(IndexTuple tup,
 				 * either an aligned or unaligned value.
 				 */
 				if (usecache &&
-					off == att_align_nominal(off, att[i]->attalign))
-					att[i]->attcacheoff = off;
+					off == att_align_nominal(off, att->attalign))
+					att->attcacheoff = off;
 				else
 				{
-					off = att_align_pointer(off, att[i]->attalign, -1,
+					off = att_align_pointer(off, att->attalign, -1,
 											tp + off);
 					usecache = false;
 				}
@@ -387,23 +391,23 @@ nocache_index_getattr(IndexTuple tup,
 			else
 			{
 				/* not varlena, so safe to use att_align_nominal */
-				off = att_align_nominal(off, att[i]->attalign);
+				off = att_align_nominal(off, att->attalign);
 
 				if (usecache)
-					att[i]->attcacheoff = off;
+					att->attcacheoff = off;
 			}
 
 			if (i == attnum)
 				break;
 
-			off = att_addlength_pointer(off, att[i]->attlen, tp + off);
+			off = att_addlength_pointer(off, att->attlen, tp + off);
 
-			if (usecache && att[i]->attlen <= 0)
+			if (usecache && att->attlen <= 0)
 				usecache = false;
 		}
 	}
 
-	return fetchatt(att[attnum], tp + off);
+	return fetchatt(TupleDescAttr(tupleDesc, attnum), tp + off);
 }
 
 /*
diff --git a/src/backend/access/common/printsimple.c b/src/backend/access/common/printsimple.c
index c863e859fef16c3422352014338692b9caab16db..b3e9a26b0327c69d1441ae5eb3934cffdfff0e06 100644
--- a/src/backend/access/common/printsimple.c
+++ b/src/backend/access/common/printsimple.c
@@ -38,7 +38,7 @@ printsimple_startup(DestReceiver *self, int operation, TupleDesc tupdesc)
 
 	for (i = 0; i < tupdesc->natts; ++i)
 	{
-		Form_pg_attribute attr = tupdesc->attrs[i];
+		Form_pg_attribute attr = TupleDescAttr(tupdesc, i);
 
 		pq_sendstring(&buf, NameStr(attr->attname));
 		pq_sendint(&buf, 0, 4); /* table oid */
@@ -71,7 +71,7 @@ printsimple(TupleTableSlot *slot, DestReceiver *self)
 
 	for (i = 0; i < tupdesc->natts; ++i)
 	{
-		Form_pg_attribute attr = tupdesc->attrs[i];
+		Form_pg_attribute attr = TupleDescAttr(tupdesc, i);
 		Datum		value;
 
 		if (slot->tts_isnull[i])
diff --git a/src/backend/access/common/printtup.c b/src/backend/access/common/printtup.c
index a2ca2d74aefb36f5f71b80cadec2ca989f30d072..20d20e623e80d805395ecd9f32a2bacaf51f1294 100644
--- a/src/backend/access/common/printtup.c
+++ b/src/backend/access/common/printtup.c
@@ -187,7 +187,6 @@ printtup_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
 void
 SendRowDescriptionMessage(TupleDesc typeinfo, List *targetlist, int16 *formats)
 {
-	Form_pg_attribute *attrs = typeinfo->attrs;
 	int			natts = typeinfo->natts;
 	int			proto = PG_PROTOCOL_MAJOR(FrontendProtocol);
 	int			i;
@@ -199,10 +198,11 @@ SendRowDescriptionMessage(TupleDesc typeinfo, List *targetlist, int16 *formats)
 
 	for (i = 0; i < natts; ++i)
 	{
-		Oid			atttypid = attrs[i]->atttypid;
-		int32		atttypmod = attrs[i]->atttypmod;
+		Form_pg_attribute att = TupleDescAttr(typeinfo, i);
+		Oid			atttypid = att->atttypid;
+		int32		atttypmod = att->atttypmod;
 
-		pq_sendstring(&buf, NameStr(attrs[i]->attname));
+		pq_sendstring(&buf, NameStr(att->attname));
 		/* column ID info appears in protocol 3.0 and up */
 		if (proto >= 3)
 		{
@@ -228,7 +228,7 @@ SendRowDescriptionMessage(TupleDesc typeinfo, List *targetlist, int16 *formats)
 		/* If column is a domain, send the base type and typmod instead */
 		atttypid = getBaseTypeAndTypmod(atttypid, &atttypmod);
 		pq_sendint(&buf, (int) atttypid, sizeof(atttypid));
-		pq_sendint(&buf, attrs[i]->attlen, sizeof(attrs[i]->attlen));
+		pq_sendint(&buf, att->attlen, sizeof(att->attlen));
 		pq_sendint(&buf, atttypmod, sizeof(atttypmod));
 		/* format info appears in protocol 3.0 and up */
 		if (proto >= 3)
@@ -268,18 +268,19 @@ printtup_prepare_info(DR_printtup *myState, TupleDesc typeinfo, int numAttrs)
 	{
 		PrinttupAttrInfo *thisState = myState->myinfo + i;
 		int16		format = (formats ? formats[i] : 0);
+		Form_pg_attribute attr = TupleDescAttr(typeinfo, i);
 
 		thisState->format = format;
 		if (format == 0)
 		{
-			getTypeOutputInfo(typeinfo->attrs[i]->atttypid,
+			getTypeOutputInfo(attr->atttypid,
 							  &thisState->typoutput,
 							  &thisState->typisvarlena);
 			fmgr_info(thisState->typoutput, &thisState->finfo);
 		}
 		else if (format == 1)
 		{
-			getTypeBinaryOutputInfo(typeinfo->attrs[i]->atttypid,
+			getTypeBinaryOutputInfo(attr->atttypid,
 									&thisState->typsend,
 									&thisState->typisvarlena);
 			fmgr_info(thisState->typsend, &thisState->finfo);
@@ -513,14 +514,13 @@ void
 debugStartup(DestReceiver *self, int operation, TupleDesc typeinfo)
 {
 	int			natts = typeinfo->natts;
-	Form_pg_attribute *attinfo = typeinfo->attrs;
 	int			i;
 
 	/*
 	 * show the return type of the tuples
 	 */
 	for (i = 0; i < natts; ++i)
-		printatt((unsigned) i + 1, attinfo[i], NULL);
+		printatt((unsigned) i + 1, TupleDescAttr(typeinfo, i), NULL);
 	printf("\t----\n");
 }
 
@@ -545,12 +545,12 @@ debugtup(TupleTableSlot *slot, DestReceiver *self)
 		attr = slot_getattr(slot, i + 1, &isnull);
 		if (isnull)
 			continue;
-		getTypeOutputInfo(typeinfo->attrs[i]->atttypid,
+		getTypeOutputInfo(TupleDescAttr(typeinfo, i)->atttypid,
 						  &typoutput, &typisvarlena);
 
 		value = OidOutputFunctionCall(typoutput, attr);
 
-		printatt((unsigned) i + 1, typeinfo->attrs[i], value);
+		printatt((unsigned) i + 1, TupleDescAttr(typeinfo, i), value);
 	}
 	printf("\t----\n");
 
diff --git a/src/backend/access/common/tupconvert.c b/src/backend/access/common/tupconvert.c
index 57e44375eabcf63409e2ed6752bc584a0ac7749d..3d1bc0635bfb314ff292afff51cbef9413f3a5b2 100644
--- a/src/backend/access/common/tupconvert.c
+++ b/src/backend/access/common/tupconvert.c
@@ -84,7 +84,7 @@ convert_tuples_by_position(TupleDesc indesc,
 	same = true;
 	for (i = 0; i < n; i++)
 	{
-		Form_pg_attribute att = outdesc->attrs[i];
+		Form_pg_attribute att = TupleDescAttr(outdesc, i);
 		Oid			atttypid;
 		int32		atttypmod;
 
@@ -95,7 +95,7 @@ convert_tuples_by_position(TupleDesc indesc,
 		atttypmod = att->atttypmod;
 		for (; j < indesc->natts; j++)
 		{
-			att = indesc->attrs[j];
+			att = TupleDescAttr(indesc, j);
 			if (att->attisdropped)
 				continue;
 			nincols++;
@@ -122,7 +122,7 @@ convert_tuples_by_position(TupleDesc indesc,
 	/* Check for unused input columns */
 	for (; j < indesc->natts; j++)
 	{
-		if (indesc->attrs[j]->attisdropped)
+		if (TupleDescAttr(indesc, j)->attisdropped)
 			continue;
 		nincols++;
 		same = false;			/* we'll complain below */
@@ -149,6 +149,9 @@ convert_tuples_by_position(TupleDesc indesc,
 	{
 		for (i = 0; i < n; i++)
 		{
+			Form_pg_attribute inatt;
+			Form_pg_attribute outatt;
+
 			if (attrMap[i] == (i + 1))
 				continue;
 
@@ -157,10 +160,12 @@ convert_tuples_by_position(TupleDesc indesc,
 			 * also dropped, we needn't convert.  However, attlen and attalign
 			 * must agree.
 			 */
+			inatt = TupleDescAttr(indesc, i);
+			outatt = TupleDescAttr(outdesc, i);
 			if (attrMap[i] == 0 &&
-				indesc->attrs[i]->attisdropped &&
-				indesc->attrs[i]->attlen == outdesc->attrs[i]->attlen &&
-				indesc->attrs[i]->attalign == outdesc->attrs[i]->attalign)
+				inatt->attisdropped &&
+				inatt->attlen == outatt->attlen &&
+				inatt->attalign == outatt->attalign)
 				continue;
 
 			same = false;
@@ -228,6 +233,9 @@ convert_tuples_by_name(TupleDesc indesc,
 		same = true;
 		for (i = 0; i < n; i++)
 		{
+			Form_pg_attribute inatt;
+			Form_pg_attribute outatt;
+
 			if (attrMap[i] == (i + 1))
 				continue;
 
@@ -236,10 +244,12 @@ convert_tuples_by_name(TupleDesc indesc,
 			 * also dropped, we needn't convert.  However, attlen and attalign
 			 * must agree.
 			 */
+			inatt = TupleDescAttr(indesc, i);
+			outatt = TupleDescAttr(outdesc, i);
 			if (attrMap[i] == 0 &&
-				indesc->attrs[i]->attisdropped &&
-				indesc->attrs[i]->attlen == outdesc->attrs[i]->attlen &&
-				indesc->attrs[i]->attalign == outdesc->attrs[i]->attalign)
+				inatt->attisdropped &&
+				inatt->attlen == outatt->attlen &&
+				inatt->attalign == outatt->attalign)
 				continue;
 
 			same = false;
@@ -292,26 +302,27 @@ convert_tuples_by_name_map(TupleDesc indesc,
 	attrMap = (AttrNumber *) palloc0(n * sizeof(AttrNumber));
 	for (i = 0; i < n; i++)
 	{
-		Form_pg_attribute att = outdesc->attrs[i];
+		Form_pg_attribute outatt = TupleDescAttr(outdesc, i);
 		char	   *attname;
 		Oid			atttypid;
 		int32		atttypmod;
 		int			j;
 
-		if (att->attisdropped)
+		if (outatt->attisdropped)
 			continue;			/* attrMap[i] is already 0 */
-		attname = NameStr(att->attname);
-		atttypid = att->atttypid;
-		atttypmod = att->atttypmod;
+		attname = NameStr(outatt->attname);
+		atttypid = outatt->atttypid;
+		atttypmod = outatt->atttypmod;
 		for (j = 0; j < indesc->natts; j++)
 		{
-			att = indesc->attrs[j];
-			if (att->attisdropped)
+			Form_pg_attribute inatt = TupleDescAttr(indesc, j);
+
+			if (inatt->attisdropped)
 				continue;
-			if (strcmp(attname, NameStr(att->attname)) == 0)
+			if (strcmp(attname, NameStr(inatt->attname)) == 0)
 			{
 				/* Found it, check type */
-				if (atttypid != att->atttypid || atttypmod != att->atttypmod)
+				if (atttypid != inatt->atttypid || atttypmod != inatt->atttypmod)
 					ereport(ERROR,
 							(errcode(ERRCODE_DATATYPE_MISMATCH),
 							 errmsg_internal("%s", _(msg)),
diff --git a/src/backend/access/common/tupdesc.c b/src/backend/access/common/tupdesc.c
index 9fd7b4e019bbfdb6198951ebcb30dc5b5e2f623a..a5df2d64e2880d61d5f83627b5627335b4b2d1e1 100644
--- a/src/backend/access/common/tupdesc.c
+++ b/src/backend/access/common/tupdesc.c
@@ -175,7 +175,9 @@ CreateTupleDescCopyConstr(TupleDesc tupdesc)
 
 	for (i = 0; i < desc->natts; i++)
 	{
-		memcpy(desc->attrs[i], tupdesc->attrs[i], ATTRIBUTE_FIXED_PART_SIZE);
+		memcpy(TupleDescAttr(desc, i),
+			   TupleDescAttr(tupdesc, i),
+			   ATTRIBUTE_FIXED_PART_SIZE);
 	}
 
 	if (constr)
@@ -230,6 +232,9 @@ void
 TupleDescCopyEntry(TupleDesc dst, AttrNumber dstAttno,
 				   TupleDesc src, AttrNumber srcAttno)
 {
+	Form_pg_attribute dstAtt = TupleDescAttr(dst, dstAttno - 1);
+	Form_pg_attribute srcAtt = TupleDescAttr(src, srcAttno - 1);
+
 	/*
 	 * sanity checks
 	 */
@@ -240,8 +245,7 @@ TupleDescCopyEntry(TupleDesc dst, AttrNumber dstAttno,
 	AssertArg(dstAttno >= 1);
 	AssertArg(dstAttno <= dst->natts);
 
-	memcpy(dst->attrs[dstAttno - 1], src->attrs[srcAttno - 1],
-		   ATTRIBUTE_FIXED_PART_SIZE);
+	memcpy(dstAtt, srcAtt, ATTRIBUTE_FIXED_PART_SIZE);
 
 	/*
 	 * Aside from updating the attno, we'd better reset attcacheoff.
@@ -252,13 +256,13 @@ TupleDescCopyEntry(TupleDesc dst, AttrNumber dstAttno,
 	 * by other uses of this function or TupleDescInitEntry.  So we cheat a
 	 * bit to avoid a useless O(N^2) penalty.
 	 */
-	dst->attrs[dstAttno - 1]->attnum = dstAttno;
-	dst->attrs[dstAttno - 1]->attcacheoff = -1;
+	dstAtt->attnum = dstAttno;
+	dstAtt->attcacheoff = -1;
 
 	/* since we're not copying constraints or defaults, clear these */
-	dst->attrs[dstAttno - 1]->attnotnull = false;
-	dst->attrs[dstAttno - 1]->atthasdef = false;
-	dst->attrs[dstAttno - 1]->attidentity = '\0';
+	dstAtt->attnotnull = false;
+	dstAtt->atthasdef = false;
+	dstAtt->attidentity = '\0';
 }
 
 /*
@@ -366,8 +370,8 @@ equalTupleDescs(TupleDesc tupdesc1, TupleDesc tupdesc2)
 
 	for (i = 0; i < tupdesc1->natts; i++)
 	{
-		Form_pg_attribute attr1 = tupdesc1->attrs[i];
-		Form_pg_attribute attr2 = tupdesc2->attrs[i];
+		Form_pg_attribute attr1 = TupleDescAttr(tupdesc1, i);
+		Form_pg_attribute attr2 = TupleDescAttr(tupdesc2, i);
 
 		/*
 		 * We do not need to check every single field here: we can disregard
@@ -515,7 +519,7 @@ TupleDescInitEntry(TupleDesc desc,
 	/*
 	 * initialize the attribute fields
 	 */
-	att = desc->attrs[attributeNumber - 1];
+	att = TupleDescAttr(desc, attributeNumber - 1);
 
 	att->attrelid = 0;			/* dummy value */
 
@@ -580,7 +584,7 @@ TupleDescInitBuiltinEntry(TupleDesc desc,
 	AssertArg(attributeNumber <= desc->natts);
 
 	/* initialize the attribute fields */
-	att = desc->attrs[attributeNumber - 1];
+	att = TupleDescAttr(desc, attributeNumber - 1);
 	att->attrelid = 0;			/* dummy value */
 
 	/* unlike TupleDescInitEntry, we require an attribute name */
@@ -664,7 +668,7 @@ TupleDescInitEntryCollation(TupleDesc desc,
 	AssertArg(attributeNumber >= 1);
 	AssertArg(attributeNumber <= desc->natts);
 
-	desc->attrs[attributeNumber - 1]->attcollation = collationid;
+	TupleDescAttr(desc, attributeNumber - 1)->attcollation = collationid;
 }
 
 
@@ -704,6 +708,7 @@ BuildDescForRelation(List *schema)
 	{
 		ColumnDef  *entry = lfirst(l);
 		AclResult	aclresult;
+		Form_pg_attribute att;
 
 		/*
 		 * for each entry in the list, get the name and type information from
@@ -730,17 +735,18 @@ BuildDescForRelation(List *schema)
 
 		TupleDescInitEntry(desc, attnum, attname,
 						   atttypid, atttypmod, attdim);
+		att = TupleDescAttr(desc, attnum - 1);
 
 		/* Override TupleDescInitEntry's settings as requested */
 		TupleDescInitEntryCollation(desc, attnum, attcollation);
 		if (entry->storage)
-			desc->attrs[attnum - 1]->attstorage = entry->storage;
+			att->attstorage = entry->storage;
 
 		/* Fill in additional stuff not handled by TupleDescInitEntry */
-		desc->attrs[attnum - 1]->attnotnull = entry->is_not_null;
+		att->attnotnull = entry->is_not_null;
 		has_not_null |= entry->is_not_null;
-		desc->attrs[attnum - 1]->attislocal = entry->is_local;
-		desc->attrs[attnum - 1]->attinhcount = entry->inhcount;
+		att->attislocal = entry->is_local;
+		att->attinhcount = entry->inhcount;
 	}
 
 	if (has_not_null)
diff --git a/src/backend/access/gin/ginbulk.c b/src/backend/access/gin/ginbulk.c
index 4ff149e59afa8f49c79b8c9cb3127efeaa9e3f83..c76f5042950ec227d6e3543163e8a6032df4c9fb 100644
--- a/src/backend/access/gin/ginbulk.c
+++ b/src/backend/access/gin/ginbulk.c
@@ -127,9 +127,10 @@ ginInitBA(BuildAccumulator *accum)
 static Datum
 getDatumCopy(BuildAccumulator *accum, OffsetNumber attnum, Datum value)
 {
-	Form_pg_attribute att = accum->ginstate->origTupdesc->attrs[attnum - 1];
+	Form_pg_attribute att;
 	Datum		res;
 
+	att = TupleDescAttr(accum->ginstate->origTupdesc, attnum - 1);
 	if (att->attbyval)
 		res = value;
 	else
diff --git a/src/backend/access/gin/ginget.c b/src/backend/access/gin/ginget.c
index 56a5bf47b8cd5bfc619fb6593bd873bf2471db1a..9895080685266a868be05f90fea30e03b76cc6e5 100644
--- a/src/backend/access/gin/ginget.c
+++ b/src/backend/access/gin/ginget.c
@@ -129,7 +129,7 @@ collectMatchBitmap(GinBtreeData *btree, GinBtreeStack *stack,
 
 	/* Locate tupdesc entry for key column (for attbyval/attlen data) */
 	attnum = scanEntry->attnum;
-	attr = btree->ginstate->origTupdesc->attrs[attnum - 1];
+	attr = TupleDescAttr(btree->ginstate->origTupdesc, attnum - 1);
 
 	for (;;)
 	{
diff --git a/src/backend/access/gin/ginutil.c b/src/backend/access/gin/ginutil.c
index 91e4a8cf700d526c8b435df00740d265195bd8e9..136ea277180e081b161717741205db98660cd014 100644
--- a/src/backend/access/gin/ginutil.c
+++ b/src/backend/access/gin/ginutil.c
@@ -96,6 +96,8 @@ initGinState(GinState *state, Relation index)
 
 	for (i = 0; i < origTupdesc->natts; i++)
 	{
+		Form_pg_attribute attr = TupleDescAttr(origTupdesc, i);
+
 		if (state->oneCol)
 			state->tupdesc[i] = state->origTupdesc;
 		else
@@ -105,11 +107,11 @@ initGinState(GinState *state, Relation index)
 			TupleDescInitEntry(state->tupdesc[i], (AttrNumber) 1, NULL,
 							   INT2OID, -1, 0);
 			TupleDescInitEntry(state->tupdesc[i], (AttrNumber) 2, NULL,
-							   origTupdesc->attrs[i]->atttypid,
-							   origTupdesc->attrs[i]->atttypmod,
-							   origTupdesc->attrs[i]->attndims);
+							   attr->atttypid,
+							   attr->atttypmod,
+							   attr->attndims);
 			TupleDescInitEntryCollation(state->tupdesc[i], (AttrNumber) 2,
-										origTupdesc->attrs[i]->attcollation);
+										attr->attcollation);
 		}
 
 		/*
@@ -126,13 +128,13 @@ initGinState(GinState *state, Relation index)
 		{
 			TypeCacheEntry *typentry;
 
-			typentry = lookup_type_cache(origTupdesc->attrs[i]->atttypid,
+			typentry = lookup_type_cache(attr->atttypid,
 										 TYPECACHE_CMP_PROC_FINFO);
 			if (!OidIsValid(typentry->cmp_proc_finfo.fn_oid))
 				ereport(ERROR,
 						(errcode(ERRCODE_UNDEFINED_FUNCTION),
 						 errmsg("could not identify a comparison function for type %s",
-								format_type_be(origTupdesc->attrs[i]->atttypid))));
+								format_type_be(attr->atttypid))));
 			fmgr_info_copy(&(state->compareFn[i]),
 						   &(typentry->cmp_proc_finfo),
 						   CurrentMemoryContext);
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 4756a70ae6de0458d10d244ef8a9254b29bbf74b..b4cb36486908b9d247a2e742419cf8e0c5404f7b 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -295,10 +295,10 @@ gistInitBuffering(GISTBuildState *buildstate)
 	itupMinSize = (Size) MAXALIGN(sizeof(IndexTupleData));
 	for (i = 0; i < index->rd_att->natts; i++)
 	{
-		if (index->rd_att->attrs[i]->attlen < 0)
+		if (TupleDescAttr(index->rd_att, i)->attlen < 0)
 			itupMinSize += VARHDRSZ;
 		else
-			itupMinSize += index->rd_att->attrs[i]->attlen;
+			itupMinSize += TupleDescAttr(index->rd_att, i)->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 8792f1453cb96ced3c999b314413d0885bfe5895..ff03c68fcdb570f53de02f681acbdfab14a79814 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -1066,11 +1066,11 @@ fastgetattr(HeapTuple tup, int attnum, TupleDesc tupleDesc,
 			 (*(isnull) = false),
 			 HeapTupleNoNulls(tup) ?
 			 (
-			  (tupleDesc)->attrs[(attnum) - 1]->attcacheoff >= 0 ?
+			  TupleDescAttr((tupleDesc), (attnum) - 1)->attcacheoff >= 0 ?
 			  (
-			   fetchatt((tupleDesc)->attrs[(attnum) - 1],
+			   fetchatt(TupleDescAttr((tupleDesc), (attnum) - 1),
 						(char *) (tup)->t_data + (tup)->t_data->t_hoff +
-						(tupleDesc)->attrs[(attnum) - 1]->attcacheoff)
+						TupleDescAttr((tupleDesc), (attnum) - 1)->attcacheoff)
 			   )
 			  :
 			  nocachegetattr((tup), (attnum), (tupleDesc))
@@ -4422,7 +4422,7 @@ heap_tuple_attr_equals(TupleDesc tupdesc, int attrnum,
 	else
 	{
 		Assert(attrnum <= tupdesc->natts);
-		att = tupdesc->attrs[attrnum - 1];
+		att = TupleDescAttr(tupdesc, attrnum - 1);
 		return datumIsEqual(value1, value2, att->attbyval, att->attlen);
 	}
 }
diff --git a/src/backend/access/heap/tuptoaster.c b/src/backend/access/heap/tuptoaster.c
index 458180bc95fe5f7bc691ae474f65aa7389c42ba2..5a8f1dab83b60642dc915a1850578af4dbf4ade8 100644
--- a/src/backend/access/heap/tuptoaster.c
+++ b/src/backend/access/heap/tuptoaster.c
@@ -464,7 +464,6 @@ void
 toast_delete(Relation rel, HeapTuple oldtup, bool is_speculative)
 {
 	TupleDesc	tupleDesc;
-	Form_pg_attribute *att;
 	int			numAttrs;
 	int			i;
 	Datum		toast_values[MaxHeapAttributeNumber];
@@ -489,7 +488,6 @@ toast_delete(Relation rel, HeapTuple oldtup, bool is_speculative)
 	 * least one varlena column, by the way.)
 	 */
 	tupleDesc = rel->rd_att;
-	att = tupleDesc->attrs;
 	numAttrs = tupleDesc->natts;
 
 	Assert(numAttrs <= MaxHeapAttributeNumber);
@@ -501,7 +499,7 @@ toast_delete(Relation rel, HeapTuple oldtup, bool is_speculative)
 	 */
 	for (i = 0; i < numAttrs; i++)
 	{
-		if (att[i]->attlen == -1)
+		if (TupleDescAttr(tupleDesc, i)->attlen == -1)
 		{
 			Datum		value = toast_values[i];
 
@@ -538,7 +536,6 @@ toast_insert_or_update(Relation rel, HeapTuple newtup, HeapTuple oldtup,
 {
 	HeapTuple	result_tuple;
 	TupleDesc	tupleDesc;
-	Form_pg_attribute *att;
 	int			numAttrs;
 	int			i;
 
@@ -579,7 +576,6 @@ toast_insert_or_update(Relation rel, HeapTuple newtup, HeapTuple oldtup,
 	 * Get the tuple descriptor and break down the tuple(s) into fields.
 	 */
 	tupleDesc = rel->rd_att;
-	att = tupleDesc->attrs;
 	numAttrs = tupleDesc->natts;
 
 	Assert(numAttrs <= MaxHeapAttributeNumber);
@@ -606,6 +602,7 @@ toast_insert_or_update(Relation rel, HeapTuple newtup, HeapTuple oldtup,
 
 	for (i = 0; i < numAttrs; i++)
 	{
+		Form_pg_attribute att = TupleDescAttr(tupleDesc, i);
 		struct varlena *old_value;
 		struct varlena *new_value;
 
@@ -621,7 +618,7 @@ toast_insert_or_update(Relation rel, HeapTuple newtup, HeapTuple oldtup,
 			 * If the old value is stored on disk, check if it has changed so
 			 * we have to delete it later.
 			 */
-			if (att[i]->attlen == -1 && !toast_oldisnull[i] &&
+			if (att->attlen == -1 && !toast_oldisnull[i] &&
 				VARATT_IS_EXTERNAL_ONDISK(old_value))
 			{
 				if (toast_isnull[i] || !VARATT_IS_EXTERNAL_ONDISK(new_value) ||
@@ -668,12 +665,12 @@ toast_insert_or_update(Relation rel, HeapTuple newtup, HeapTuple oldtup,
 		/*
 		 * Now look at varlena attributes
 		 */
-		if (att[i]->attlen == -1)
+		if (att->attlen == -1)
 		{
 			/*
 			 * If the table's attribute says PLAIN always, force it so.
 			 */
-			if (att[i]->attstorage == 'p')
+			if (att->attstorage == 'p')
 				toast_action[i] = 'p';
 
 			/*
@@ -687,7 +684,7 @@ toast_insert_or_update(Relation rel, HeapTuple newtup, HeapTuple oldtup,
 			if (VARATT_IS_EXTERNAL(new_value))
 			{
 				toast_oldexternal[i] = new_value;
-				if (att[i]->attstorage == 'p')
+				if (att->attstorage == 'p')
 					new_value = heap_tuple_untoast_attr(new_value);
 				else
 					new_value = heap_tuple_fetch_attr(new_value);
@@ -749,13 +746,15 @@ toast_insert_or_update(Relation rel, HeapTuple newtup, HeapTuple oldtup,
 		 */
 		for (i = 0; i < numAttrs; i++)
 		{
+			Form_pg_attribute att = TupleDescAttr(tupleDesc, i);
+
 			if (toast_action[i] != ' ')
 				continue;
 			if (VARATT_IS_EXTERNAL(DatumGetPointer(toast_values[i])))
 				continue;		/* can't happen, toast_action would be 'p' */
 			if (VARATT_IS_COMPRESSED(DatumGetPointer(toast_values[i])))
 				continue;
-			if (att[i]->attstorage != 'x' && att[i]->attstorage != 'e')
+			if (att->attstorage != 'x' && att->attstorage != 'e')
 				continue;
 			if (toast_sizes[i] > biggest_size)
 			{
@@ -771,7 +770,7 @@ toast_insert_or_update(Relation rel, HeapTuple newtup, HeapTuple oldtup,
 		 * Attempt to compress it inline, if it has attstorage 'x'
 		 */
 		i = biggest_attno;
-		if (att[i]->attstorage == 'x')
+		if (TupleDescAttr(tupleDesc, i)->attstorage == 'x')
 		{
 			old_value = toast_values[i];
 			new_value = toast_compress_datum(old_value);
@@ -841,11 +840,13 @@ toast_insert_or_update(Relation rel, HeapTuple newtup, HeapTuple oldtup,
 		 */
 		for (i = 0; i < numAttrs; i++)
 		{
+			Form_pg_attribute att = TupleDescAttr(tupleDesc, i);
+
 			if (toast_action[i] == 'p')
 				continue;
 			if (VARATT_IS_EXTERNAL(DatumGetPointer(toast_values[i])))
 				continue;		/* can't happen, toast_action would be 'p' */
-			if (att[i]->attstorage != 'x' && att[i]->attstorage != 'e')
+			if (att->attstorage != 'x' && att->attstorage != 'e')
 				continue;
 			if (toast_sizes[i] > biggest_size)
 			{
@@ -896,7 +897,7 @@ toast_insert_or_update(Relation rel, HeapTuple newtup, HeapTuple oldtup,
 				continue;		/* can't happen, toast_action would be 'p' */
 			if (VARATT_IS_COMPRESSED(DatumGetPointer(toast_values[i])))
 				continue;
-			if (att[i]->attstorage != 'm')
+			if (TupleDescAttr(tupleDesc, i)->attstorage != 'm')
 				continue;
 			if (toast_sizes[i] > biggest_size)
 			{
@@ -959,7 +960,7 @@ toast_insert_or_update(Relation rel, HeapTuple newtup, HeapTuple oldtup,
 				continue;
 			if (VARATT_IS_EXTERNAL(DatumGetPointer(toast_values[i])))
 				continue;		/* can't happen, toast_action would be 'p' */
-			if (att[i]->attstorage != 'm')
+			if (TupleDescAttr(tupleDesc, i)->attstorage != 'm')
 				continue;
 			if (toast_sizes[i] > biggest_size)
 			{
@@ -1084,7 +1085,6 @@ HeapTuple
 toast_flatten_tuple(HeapTuple tup, TupleDesc tupleDesc)
 {
 	HeapTuple	new_tuple;
-	Form_pg_attribute *att = tupleDesc->attrs;
 	int			numAttrs = tupleDesc->natts;
 	int			i;
 	Datum		toast_values[MaxTupleAttributeNumber];
@@ -1104,7 +1104,7 @@ toast_flatten_tuple(HeapTuple tup, TupleDesc tupleDesc)
 		/*
 		 * Look at non-null varlena attributes
 		 */
-		if (!toast_isnull[i] && att[i]->attlen == -1)
+		if (!toast_isnull[i] && TupleDescAttr(tupleDesc, i)->attlen == -1)
 		{
 			struct varlena *new_value;
 
@@ -1193,7 +1193,6 @@ toast_flatten_tuple_to_datum(HeapTupleHeader tup,
 	int32		new_data_len;
 	int32		new_tuple_len;
 	HeapTupleData tmptup;
-	Form_pg_attribute *att = tupleDesc->attrs;
 	int			numAttrs = tupleDesc->natts;
 	int			i;
 	bool		has_nulls = false;
@@ -1222,7 +1221,7 @@ toast_flatten_tuple_to_datum(HeapTupleHeader tup,
 		 */
 		if (toast_isnull[i])
 			has_nulls = true;
-		else if (att[i]->attlen == -1)
+		else if (TupleDescAttr(tupleDesc, i)->attlen == -1)
 		{
 			struct varlena *new_value;
 
@@ -1307,7 +1306,6 @@ toast_build_flattened_tuple(TupleDesc tupleDesc,
 							bool *isnull)
 {
 	HeapTuple	new_tuple;
-	Form_pg_attribute *att = tupleDesc->attrs;
 	int			numAttrs = tupleDesc->natts;
 	int			num_to_free;
 	int			i;
@@ -1327,7 +1325,7 @@ toast_build_flattened_tuple(TupleDesc tupleDesc,
 		/*
 		 * Look at non-null varlena attributes
 		 */
-		if (!isnull[i] && att[i]->attlen == -1)
+		if (!isnull[i] && TupleDescAttr(tupleDesc, i)->attlen == -1)
 		{
 			struct varlena *new_value;
 
diff --git a/src/backend/access/spgist/spgutils.c b/src/backend/access/spgist/spgutils.c
index 8656af453c1a23cdfa0f13e4f33a8e4c117ea104..22f64b0103c6e5a2dad66d59f1ba2beed5332453 100644
--- a/src/backend/access/spgist/spgutils.c
+++ b/src/backend/access/spgist/spgutils.c
@@ -112,7 +112,7 @@ spgGetCache(Relation index)
 		 * tupdesc.  We pass this to the opclass config function so that
 		 * polymorphic opclasses are possible.
 		 */
-		atttype = index->rd_att->attrs[0]->atttypid;
+		atttype = TupleDescAttr(index->rd_att, 0)->atttypid;
 
 		/* Call the config function to get config info for the opclass */
 		in.attType = atttype;
diff --git a/src/backend/bootstrap/bootstrap.c b/src/backend/bootstrap/bootstrap.c
index b3f0b3cc92271e58d0e7ceea26fcf61d0999860f..0453fd4ac1036fe857b41f09a7b0d0db1058c9bd 100644
--- a/src/backend/bootstrap/bootstrap.c
+++ b/src/backend/bootstrap/bootstrap.c
@@ -609,7 +609,7 @@ boot_openrel(char *relname)
 		if (attrtypes[i] == NULL)
 			attrtypes[i] = AllocateAttribute();
 		memmove((char *) attrtypes[i],
-				(char *) boot_reldesc->rd_att->attrs[i],
+				(char *) TupleDescAttr(boot_reldesc->rd_att, i),
 				ATTRIBUTE_FIXED_PART_SIZE);
 
 		{
@@ -816,7 +816,7 @@ InsertOneValue(char *value, int i)
 
 	elog(DEBUG4, "inserting column %d value \"%s\"", i, value);
 
-	typoid = boot_reldesc->rd_att->attrs[i]->atttypid;
+	typoid = TupleDescAttr(boot_reldesc->rd_att, i)->atttypid;
 
 	boot_get_type_io_data(typoid,
 						  &typlen, &typbyval, &typalign,
@@ -843,10 +843,10 @@ InsertOneNull(int i)
 {
 	elog(DEBUG4, "inserting column %d NULL", i);
 	Assert(i >= 0 && i < MAXATTR);
-	if (boot_reldesc->rd_att->attrs[i]->attnotnull)
+	if (TupleDescAttr(boot_reldesc->rd_att, i)->attnotnull)
 		elog(ERROR,
 			 "NULL value specified for not-null column \"%s\" of relation \"%s\"",
-			 NameStr(boot_reldesc->rd_att->attrs[i]->attname),
+			 NameStr(TupleDescAttr(boot_reldesc->rd_att, i)->attname),
 			 RelationGetRelationName(boot_reldesc));
 	values[i] = PointerGetDatum(NULL);
 	Nulls[i] = true;
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index a376b99f1ed49f21f6d0e03a8de0cdf288e8cad9..45ee9ac8b9cb8045e44b744b7de6627370482932 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -431,12 +431,14 @@ CheckAttributeNamesTypes(TupleDesc tupdesc, char relkind,
 	{
 		for (i = 0; i < natts; i++)
 		{
-			if (SystemAttributeByName(NameStr(tupdesc->attrs[i]->attname),
+			Form_pg_attribute attr = TupleDescAttr(tupdesc, i);
+
+			if (SystemAttributeByName(NameStr(attr->attname),
 									  tupdesc->tdhasoid) != NULL)
 				ereport(ERROR,
 						(errcode(ERRCODE_DUPLICATE_COLUMN),
 						 errmsg("column name \"%s\" conflicts with a system column name",
-								NameStr(tupdesc->attrs[i]->attname))));
+								NameStr(attr->attname))));
 		}
 	}
 
@@ -447,12 +449,12 @@ CheckAttributeNamesTypes(TupleDesc tupdesc, char relkind,
 	{
 		for (j = 0; j < i; j++)
 		{
-			if (strcmp(NameStr(tupdesc->attrs[j]->attname),
-					   NameStr(tupdesc->attrs[i]->attname)) == 0)
+			if (strcmp(NameStr(TupleDescAttr(tupdesc, j)->attname),
+					   NameStr(TupleDescAttr(tupdesc, i)->attname)) == 0)
 				ereport(ERROR,
 						(errcode(ERRCODE_DUPLICATE_COLUMN),
 						 errmsg("column name \"%s\" specified more than once",
-								NameStr(tupdesc->attrs[j]->attname))));
+								NameStr(TupleDescAttr(tupdesc, j)->attname))));
 		}
 	}
 
@@ -461,9 +463,9 @@ CheckAttributeNamesTypes(TupleDesc tupdesc, char relkind,
 	 */
 	for (i = 0; i < natts; i++)
 	{
-		CheckAttributeType(NameStr(tupdesc->attrs[i]->attname),
-						   tupdesc->attrs[i]->atttypid,
-						   tupdesc->attrs[i]->attcollation,
+		CheckAttributeType(NameStr(TupleDescAttr(tupdesc, i)->attname),
+						   TupleDescAttr(tupdesc, i)->atttypid,
+						   TupleDescAttr(tupdesc, i)->attcollation,
 						   NIL, /* assume we're creating a new rowtype */
 						   allow_system_table_mods);
 	}
@@ -545,7 +547,7 @@ CheckAttributeType(const char *attname,
 
 		for (i = 0; i < tupdesc->natts; i++)
 		{
-			Form_pg_attribute attr = tupdesc->attrs[i];
+			Form_pg_attribute attr = TupleDescAttr(tupdesc, i);
 
 			if (attr->attisdropped)
 				continue;
@@ -678,7 +680,7 @@ AddNewAttributeTuples(Oid new_rel_oid,
 	 */
 	for (i = 0; i < natts; i++)
 	{
-		attr = tupdesc->attrs[i];
+		attr = TupleDescAttr(tupdesc, i);
 		/* Fill in the correct relation OID */
 		attr->attrelid = new_rel_oid;
 		/* Make sure these are OK, too */
@@ -2245,7 +2247,7 @@ AddRelationNewConstraints(Relation rel,
 	foreach(cell, newColDefaults)
 	{
 		RawColumnDefault *colDef = (RawColumnDefault *) lfirst(cell);
-		Form_pg_attribute atp = rel->rd_att->attrs[colDef->attnum - 1];
+		Form_pg_attribute atp = TupleDescAttr(rel->rd_att, colDef->attnum - 1);
 		Oid			defOid;
 
 		expr = cookDefault(pstate, colDef->raw_default,
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 25c5bead9f0a99db4528902b46c8ff611e2378e2..c7b2f031f09dd35478b01d7696829ed96eee7d3f 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -307,7 +307,7 @@ ConstructTupleDescriptor(Relation heapRelation,
 	for (i = 0; i < numatts; i++)
 	{
 		AttrNumber	atnum = indexInfo->ii_KeyAttrNumbers[i];
-		Form_pg_attribute to = indexTupDesc->attrs[i];
+		Form_pg_attribute to = TupleDescAttr(indexTupDesc, i);
 		HeapTuple	tuple;
 		Form_pg_type typeTup;
 		Form_pg_opclass opclassTup;
@@ -333,7 +333,8 @@ ConstructTupleDescriptor(Relation heapRelation,
 				 */
 				if (atnum > natts)	/* safety check */
 					elog(ERROR, "invalid column number %d", atnum);
-				from = heapTupDesc->attrs[AttrNumberGetAttrOffset(atnum)];
+				from = TupleDescAttr(heapTupDesc,
+									 AttrNumberGetAttrOffset(atnum));
 			}
 
 			/*
@@ -495,7 +496,7 @@ InitializeAttributeOids(Relation indexRelation,
 	tupleDescriptor = RelationGetDescr(indexRelation);
 
 	for (i = 0; i < numatts; i += 1)
-		tupleDescriptor->attrs[i]->attrelid = indexoid;
+		TupleDescAttr(tupleDescriptor, i)->attrelid = indexoid;
 }
 
 /* ----------------------------------------------------------------
@@ -524,14 +525,16 @@ AppendAttributeTuples(Relation indexRelation, int numatts)
 
 	for (i = 0; i < numatts; i++)
 	{
+		Form_pg_attribute attr = TupleDescAttr(indexTupDesc, i);
+
 		/*
 		 * There used to be very grotty code here to set these fields, but I
 		 * think it's unnecessary.  They should be set already.
 		 */
-		Assert(indexTupDesc->attrs[i]->attnum == i + 1);
-		Assert(indexTupDesc->attrs[i]->attcacheoff == -1);
+		Assert(attr->attnum == i + 1);
+		Assert(attr->attcacheoff == -1);
 
-		InsertPgAttributeTuple(pg_attribute, indexTupDesc->attrs[i], indstate);
+		InsertPgAttributeTuple(pg_attribute, attr, indstate);
 	}
 
 	CatalogCloseIndexes(indstate);
diff --git a/src/backend/catalog/toasting.c b/src/backend/catalog/toasting.c
index 29756eb14eb43f2a4f15ad8a7eda00bbdbd3158d..6f517bbcdaeaf26199a368454fb47a5bb4d9d50c 100644
--- a/src/backend/catalog/toasting.c
+++ b/src/backend/catalog/toasting.c
@@ -235,9 +235,9 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid,
 	 * toast :-(.  This is essential for chunk_data because type bytea is
 	 * toastable; hit the other two just to be sure.
 	 */
-	tupdesc->attrs[0]->attstorage = 'p';
-	tupdesc->attrs[1]->attstorage = 'p';
-	tupdesc->attrs[2]->attstorage = 'p';
+	TupleDescAttr(tupdesc, 0)->attstorage = 'p';
+	TupleDescAttr(tupdesc, 1)->attstorage = 'p';
+	TupleDescAttr(tupdesc, 2)->attstorage = 'p';
 
 	/*
 	 * Toast tables for regular relations go in pg_toast; those for temp
@@ -402,33 +402,33 @@ needs_toast_table(Relation rel)
 	bool		maxlength_unknown = false;
 	bool		has_toastable_attrs = false;
 	TupleDesc	tupdesc;
-	Form_pg_attribute *att;
 	int32		tuple_length;
 	int			i;
 
 	tupdesc = rel->rd_att;
-	att = tupdesc->attrs;
 
 	for (i = 0; i < tupdesc->natts; i++)
 	{
-		if (att[i]->attisdropped)
+		Form_pg_attribute att = TupleDescAttr(tupdesc, i);
+
+		if (att->attisdropped)
 			continue;
-		data_length = att_align_nominal(data_length, att[i]->attalign);
-		if (att[i]->attlen > 0)
+		data_length = att_align_nominal(data_length, att->attalign);
+		if (att->attlen > 0)
 		{
 			/* Fixed-length types are never toastable */
-			data_length += att[i]->attlen;
+			data_length += att->attlen;
 		}
 		else
 		{
-			int32		maxlen = type_maximum_size(att[i]->atttypid,
-												   att[i]->atttypmod);
+			int32		maxlen = type_maximum_size(att->atttypid,
+												   att->atttypmod);
 
 			if (maxlen < 0)
 				maxlength_unknown = true;
 			else
 				data_length += maxlen;
-			if (att[i]->attstorage != 'p')
+			if (att->attstorage != 'p')
 				has_toastable_attrs = true;
 		}
 	}
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 2b638271b3dbd85f7c95c1ec28d8f00227bdd427..fbad13ea94f0d00b6f42cf25a616c8e7664ffb7f 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -871,7 +871,7 @@ compute_index_stats(Relation onerel, double totalrows,
 static VacAttrStats *
 examine_attribute(Relation onerel, int attnum, Node *index_expr)
 {
-	Form_pg_attribute attr = onerel->rd_att->attrs[attnum - 1];
+	Form_pg_attribute attr = TupleDescAttr(onerel->rd_att, attnum - 1);
 	HeapTuple	typtuple;
 	VacAttrStats *stats;
 	int			i;
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index f51f8b94922f2ace13b03f97691a3b513727f5bb..48f1e6e2addfc4d965b1b5e2eb5b1ecd828db454 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -1714,7 +1714,7 @@ reform_and_rewrite_tuple(HeapTuple tuple,
 	/* Be sure to null out any dropped columns */
 	for (i = 0; i < newTupDesc->natts; i++)
 	{
-		if (newTupDesc->attrs[i]->attisdropped)
+		if (TupleDescAttr(newTupDesc, i)->attisdropped)
 			isnull[i] = true;
 	}
 
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 375a25fbcf843a52b19c89c1e512e6b0a65adc43..cfa3f059c2aa9ba6c43679a9bc728b52b1506ce5 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -1583,12 +1583,13 @@ BeginCopy(ParseState *pstate,
 		foreach(cur, attnums)
 		{
 			int			attnum = lfirst_int(cur);
+			Form_pg_attribute attr = TupleDescAttr(tupDesc, attnum - 1);
 
 			if (!list_member_int(cstate->attnumlist, attnum))
 				ereport(ERROR,
 						(errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
 						 errmsg("FORCE_QUOTE column \"%s\" not referenced by COPY",
-								NameStr(tupDesc->attrs[attnum - 1]->attname))));
+								NameStr(attr->attname))));
 			cstate->force_quote_flags[attnum - 1] = true;
 		}
 	}
@@ -1605,12 +1606,13 @@ BeginCopy(ParseState *pstate,
 		foreach(cur, attnums)
 		{
 			int			attnum = lfirst_int(cur);
+			Form_pg_attribute attr = TupleDescAttr(tupDesc, attnum - 1);
 
 			if (!list_member_int(cstate->attnumlist, attnum))
 				ereport(ERROR,
 						(errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
 						 errmsg("FORCE_NOT_NULL column \"%s\" not referenced by COPY",
-								NameStr(tupDesc->attrs[attnum - 1]->attname))));
+								NameStr(attr->attname))));
 			cstate->force_notnull_flags[attnum - 1] = true;
 		}
 	}
@@ -1627,12 +1629,13 @@ BeginCopy(ParseState *pstate,
 		foreach(cur, attnums)
 		{
 			int			attnum = lfirst_int(cur);
+			Form_pg_attribute attr = TupleDescAttr(tupDesc, attnum - 1);
 
 			if (!list_member_int(cstate->attnumlist, attnum))
 				ereport(ERROR,
 						(errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
 						 errmsg("FORCE_NULL column \"%s\" not referenced by COPY",
-								NameStr(tupDesc->attrs[attnum - 1]->attname))));
+								NameStr(attr->attname))));
 			cstate->force_null_flags[attnum - 1] = true;
 		}
 	}
@@ -1650,12 +1653,13 @@ BeginCopy(ParseState *pstate,
 		foreach(cur, attnums)
 		{
 			int			attnum = lfirst_int(cur);
+			Form_pg_attribute attr = TupleDescAttr(tupDesc, attnum - 1);
 
 			if (!list_member_int(cstate->attnumlist, attnum))
 				ereport(ERROR,
 						(errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
 						 errmsg_internal("selected column \"%s\" not referenced by COPY",
-										 NameStr(tupDesc->attrs[attnum - 1]->attname))));
+										 NameStr(attr->attname))));
 			cstate->convert_select_flags[attnum - 1] = true;
 		}
 	}
@@ -1919,7 +1923,6 @@ CopyTo(CopyState cstate)
 {
 	TupleDesc	tupDesc;
 	int			num_phys_attrs;
-	Form_pg_attribute *attr;
 	ListCell   *cur;
 	uint64		processed;
 
@@ -1927,7 +1930,6 @@ CopyTo(CopyState cstate)
 		tupDesc = RelationGetDescr(cstate->rel);
 	else
 		tupDesc = cstate->queryDesc->tupDesc;
-	attr = tupDesc->attrs;
 	num_phys_attrs = tupDesc->natts;
 	cstate->null_print_client = cstate->null_print; /* default */
 
@@ -1941,13 +1943,14 @@ CopyTo(CopyState cstate)
 		int			attnum = lfirst_int(cur);
 		Oid			out_func_oid;
 		bool		isvarlena;
+		Form_pg_attribute attr = TupleDescAttr(tupDesc, attnum - 1);
 
 		if (cstate->binary)
-			getTypeBinaryOutputInfo(attr[attnum - 1]->atttypid,
+			getTypeBinaryOutputInfo(attr->atttypid,
 									&out_func_oid,
 									&isvarlena);
 		else
-			getTypeOutputInfo(attr[attnum - 1]->atttypid,
+			getTypeOutputInfo(attr->atttypid,
 							  &out_func_oid,
 							  &isvarlena);
 		fmgr_info(out_func_oid, &cstate->out_functions[attnum - 1]);
@@ -2004,7 +2007,7 @@ CopyTo(CopyState cstate)
 					CopySendChar(cstate, cstate->delim[0]);
 				hdr_delim = true;
 
-				colname = NameStr(attr[attnum - 1]->attname);
+				colname = NameStr(TupleDescAttr(tupDesc, attnum - 1)->attname);
 
 				CopyAttributeOutCSV(cstate, colname, false,
 									list_length(cstate->attnumlist) == 1);
@@ -2969,7 +2972,6 @@ BeginCopyFrom(ParseState *pstate,
 	CopyState	cstate;
 	bool		pipe = (filename == NULL);
 	TupleDesc	tupDesc;
-	Form_pg_attribute *attr;
 	AttrNumber	num_phys_attrs,
 				num_defaults;
 	FmgrInfo   *in_functions;
@@ -3004,7 +3006,6 @@ BeginCopyFrom(ParseState *pstate,
 		cstate->range_table = pstate->p_rtable;
 
 	tupDesc = RelationGetDescr(cstate->rel);
-	attr = tupDesc->attrs;
 	num_phys_attrs = tupDesc->natts;
 	num_defaults = 0;
 	volatile_defexprs = false;
@@ -3022,16 +3023,18 @@ BeginCopyFrom(ParseState *pstate,
 
 	for (attnum = 1; attnum <= num_phys_attrs; attnum++)
 	{
+		Form_pg_attribute att = TupleDescAttr(tupDesc, attnum - 1);
+
 		/* We don't need info for dropped attributes */
-		if (attr[attnum - 1]->attisdropped)
+		if (att->attisdropped)
 			continue;
 
 		/* Fetch the input function and typioparam info */
 		if (cstate->binary)
-			getTypeBinaryInputInfo(attr[attnum - 1]->atttypid,
+			getTypeBinaryInputInfo(att->atttypid,
 								   &in_func_oid, &typioparams[attnum - 1]);
 		else
-			getTypeInputInfo(attr[attnum - 1]->atttypid,
+			getTypeInputInfo(att->atttypid,
 							 &in_func_oid, &typioparams[attnum - 1]);
 		fmgr_info(in_func_oid, &in_functions[attnum - 1]);
 
@@ -3273,7 +3276,6 @@ NextCopyFrom(CopyState cstate, ExprContext *econtext,
 			 Datum *values, bool *nulls, Oid *tupleOid)
 {
 	TupleDesc	tupDesc;
-	Form_pg_attribute *attr;
 	AttrNumber	num_phys_attrs,
 				attr_count,
 				num_defaults = cstate->num_defaults;
@@ -3287,7 +3289,6 @@ NextCopyFrom(CopyState cstate, ExprContext *econtext,
 	ExprState **defexprs = cstate->defexprs;
 
 	tupDesc = RelationGetDescr(cstate->rel);
-	attr = tupDesc->attrs;
 	num_phys_attrs = tupDesc->natts;
 	attr_count = list_length(cstate->attnumlist);
 	nfields = file_has_oids ? (attr_count + 1) : attr_count;
@@ -3349,12 +3350,13 @@ NextCopyFrom(CopyState cstate, ExprContext *econtext,
 		{
 			int			attnum = lfirst_int(cur);
 			int			m = attnum - 1;
+			Form_pg_attribute att = TupleDescAttr(tupDesc, m);
 
 			if (fieldno >= fldct)
 				ereport(ERROR,
 						(errcode(ERRCODE_BAD_COPY_FILE_FORMAT),
 						 errmsg("missing data for column \"%s\"",
-								NameStr(attr[m]->attname))));
+								NameStr(att->attname))));
 			string = field_strings[fieldno++];
 
 			if (cstate->convert_select_flags &&
@@ -3388,12 +3390,12 @@ NextCopyFrom(CopyState cstate, ExprContext *econtext,
 				}
 			}
 
-			cstate->cur_attname = NameStr(attr[m]->attname);
+			cstate->cur_attname = NameStr(att->attname);
 			cstate->cur_attval = string;
 			values[m] = InputFunctionCall(&in_functions[m],
 										  string,
 										  typioparams[m],
-										  attr[m]->atttypmod);
+										  att->atttypmod);
 			if (string != NULL)
 				nulls[m] = false;
 			cstate->cur_attname = NULL;
@@ -3472,14 +3474,15 @@ NextCopyFrom(CopyState cstate, ExprContext *econtext,
 		{
 			int			attnum = lfirst_int(cur);
 			int			m = attnum - 1;
+			Form_pg_attribute att = TupleDescAttr(tupDesc, m);
 
-			cstate->cur_attname = NameStr(attr[m]->attname);
+			cstate->cur_attname = NameStr(att->attname);
 			i++;
 			values[m] = CopyReadBinaryAttribute(cstate,
 												i,
 												&in_functions[m],
 												typioparams[m],
-												attr[m]->atttypmod,
+												att->atttypmod,
 												&nulls[m]);
 			cstate->cur_attname = NULL;
 		}
@@ -4709,13 +4712,12 @@ CopyGetAttnums(TupleDesc tupDesc, Relation rel, List *attnamelist)
 	if (attnamelist == NIL)
 	{
 		/* Generate default column list */
-		Form_pg_attribute *attr = tupDesc->attrs;
 		int			attr_count = tupDesc->natts;
 		int			i;
 
 		for (i = 0; i < attr_count; i++)
 		{
-			if (attr[i]->attisdropped)
+			if (TupleDescAttr(tupDesc, i)->attisdropped)
 				continue;
 			attnums = lappend_int(attnums, i + 1);
 		}
@@ -4735,11 +4737,13 @@ CopyGetAttnums(TupleDesc tupDesc, Relation rel, List *attnamelist)
 			attnum = InvalidAttrNumber;
 			for (i = 0; i < tupDesc->natts; i++)
 			{
-				if (tupDesc->attrs[i]->attisdropped)
+				Form_pg_attribute att = TupleDescAttr(tupDesc, i);
+
+				if (att->attisdropped)
 					continue;
-				if (namestrcmp(&(tupDesc->attrs[i]->attname), name) == 0)
+				if (namestrcmp(&(att->attname), name) == 0)
 				{
-					attnum = tupDesc->attrs[i]->attnum;
+					attnum = att->attnum;
 					break;
 				}
 			}
diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c
index 97f9c55d6e764e71cd64c5e65ea92f1280cc797e..e60210cb24b06b49136c5ca4bec6e03b4fdbbddd 100644
--- a/src/backend/commands/createas.c
+++ b/src/backend/commands/createas.c
@@ -468,7 +468,7 @@ intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
 	lc = list_head(into->colNames);
 	for (attnum = 0; attnum < typeinfo->natts; attnum++)
 	{
-		Form_pg_attribute attribute = typeinfo->attrs[attnum];
+		Form_pg_attribute attribute = TupleDescAttr(typeinfo, attnum);
 		ColumnDef  *col;
 		char	   *colname;
 
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 620704ec490f3909185538da0cccc72759bce306..b61aaac28427c933c251444c647a16ab3137c317 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -242,7 +242,7 @@ CheckIndexCompatible(Oid oldId,
 	for (i = 0; i < old_natts; i++)
 	{
 		if (IsPolymorphicType(get_opclass_input_type(classObjectId[i])) &&
-			irel->rd_att->attrs[i]->atttypid != typeObjectId[i])
+			TupleDescAttr(irel->rd_att, i)->atttypid != typeObjectId[i])
 		{
 			ret = false;
 			break;
@@ -270,7 +270,7 @@ CheckIndexCompatible(Oid oldId,
 
 				op_input_types(indexInfo->ii_ExclusionOps[i], &left, &right);
 				if ((IsPolymorphicType(left) || IsPolymorphicType(right)) &&
-					irel->rd_att->attrs[i]->atttypid != typeObjectId[i])
+					TupleDescAttr(irel->rd_att, i)->atttypid != typeObjectId[i])
 				{
 					ret = false;
 					break;
diff --git a/src/backend/commands/matview.c b/src/backend/commands/matview.c
index 7d57f97442ee6e1e776b9ac378cdbbbb30c72c5d..d2e0376511ac401f7d044a6914ac84049893c178 100644
--- a/src/backend/commands/matview.c
+++ b/src/backend/commands/matview.c
@@ -727,6 +727,7 @@ refresh_by_match_merge(Oid matviewOid, Oid tempOid, Oid relowner,
 			for (i = 0; i < numatts; i++)
 			{
 				int			attnum = indexStruct->indkey.values[i];
+				Form_pg_attribute attr = TupleDescAttr(tupdesc, attnum - 1);
 				Oid			type;
 				Oid			op;
 				const char *colname;
@@ -745,7 +746,7 @@ refresh_by_match_merge(Oid matviewOid, Oid tempOid, Oid relowner,
 				if (foundUniqueIndex)
 					appendStringInfoString(&querybuf, " AND ");
 
-				colname = quote_identifier(NameStr((tupdesc->attrs[attnum - 1])->attname));
+				colname = quote_identifier(NameStr(attr->attname));
 				appendStringInfo(&querybuf, "newdata.%s ", colname);
 				type = attnumTypeId(matviewRel, attnum);
 				op = lookup_type_cache(type, TYPECACHE_EQ_OPR)->eq_opr;
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 83cb46016410d96ae36b014950140ba832b370cf..0f08245a677c949db390d66e35a8e4493dc751c3 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -685,8 +685,10 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	foreach(listptr, stmt->tableElts)
 	{
 		ColumnDef  *colDef = lfirst(listptr);
+		Form_pg_attribute attr;
 
 		attnum++;
+		attr = TupleDescAttr(descriptor, attnum - 1);
 
 		if (colDef->raw_default != NULL)
 		{
@@ -698,7 +700,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 			rawEnt->attnum = attnum;
 			rawEnt->raw_default = colDef->raw_default;
 			rawDefaults = lappend(rawDefaults, rawEnt);
-			descriptor->attrs[attnum - 1]->atthasdef = true;
+			attr->atthasdef = true;
 		}
 		else if (colDef->cooked_default != NULL)
 		{
@@ -715,11 +717,11 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 			cooked->inhcount = 0;	/* ditto */
 			cooked->is_no_inherit = false;
 			cookedDefaults = lappend(cookedDefaults, cooked);
-			descriptor->attrs[attnum - 1]->atthasdef = true;
+			attr->atthasdef = true;
 		}
 
 		if (colDef->identity)
-			descriptor->attrs[attnum - 1]->attidentity = colDef->identity;
+			attr->attidentity = colDef->identity;
 	}
 
 	/*
@@ -1833,7 +1835,8 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 		for (parent_attno = 1; parent_attno <= tupleDesc->natts;
 			 parent_attno++)
 		{
-			Form_pg_attribute attribute = tupleDesc->attrs[parent_attno - 1];
+			Form_pg_attribute attribute = TupleDescAttr(tupleDesc,
+														parent_attno - 1);
 			char	   *attributeName = NameStr(attribute->attname);
 			int			exist_attno;
 			ColumnDef  *def;
@@ -4417,8 +4420,9 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 		 */
 		for (i = 0; i < newTupDesc->natts; i++)
 		{
-			if (newTupDesc->attrs[i]->attnotnull &&
-				!newTupDesc->attrs[i]->attisdropped)
+			Form_pg_attribute attr = TupleDescAttr(newTupDesc, i);
+
+			if (attr->attnotnull && !attr->attisdropped)
 				notnull_attrs = lappend_int(notnull_attrs, i);
 		}
 		if (notnull_attrs)
@@ -4482,7 +4486,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 		 */
 		for (i = 0; i < newTupDesc->natts; i++)
 		{
-			if (newTupDesc->attrs[i]->attisdropped)
+			if (TupleDescAttr(newTupDesc, i)->attisdropped)
 				dropped_attrs = lappend_int(dropped_attrs, i);
 		}
 
@@ -4556,11 +4560,15 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 				int			attn = lfirst_int(l);
 
 				if (heap_attisnull(tuple, attn + 1))
+				{
+					Form_pg_attribute attr = TupleDescAttr(newTupDesc, attn);
+
 					ereport(ERROR,
 							(errcode(ERRCODE_NOT_NULL_VIOLATION),
 							 errmsg("column \"%s\" contains null values",
-									NameStr(newTupDesc->attrs[attn]->attname)),
+									NameStr(attr->attname)),
 							 errtablecol(oldrel, attn + 1)));
+				}
 			}
 
 			foreach(l, tab->constraints)
@@ -4927,7 +4935,7 @@ find_composite_type_dependencies(Oid typeOid, Relation origRelation,
 			continue;
 
 		rel = relation_open(pg_depend->objid, AccessShareLock);
-		att = rel->rd_att->attrs[pg_depend->objsubid - 1];
+		att = TupleDescAttr(rel->rd_att, pg_depend->objsubid - 1);
 
 		if (rel->rd_rel->relkind == RELKIND_RELATION ||
 			rel->rd_rel->relkind == RELKIND_MATVIEW ||
@@ -5693,7 +5701,7 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
 		AttrNumber	parent_attnum;
 
 		parent_attnum = get_attnum(parentId, colName);
-		if (tupDesc->attrs[parent_attnum - 1]->attnotnull)
+		if (TupleDescAttr(tupDesc, parent_attnum - 1)->attnotnull)
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 					 errmsg("column \"%s\" is marked NOT NULL in parent table",
@@ -7286,13 +7294,15 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
 			CoercionPathType new_pathtype;
 			Oid			old_castfunc;
 			Oid			new_castfunc;
+			Form_pg_attribute attr = TupleDescAttr(tab->oldDesc,
+												   fkattnum[i] - 1);
 
 			/*
 			 * Identify coercion pathways from each of the old and new FK-side
 			 * column types to the right (foreign) operand type of the pfeqop.
 			 * We may assume that pg_constraint.conkey is not changing.
 			 */
-			old_fktype = tab->oldDesc->attrs[fkattnum[i] - 1]->atttypid;
+			old_fktype = attr->atttypid;
 			new_fktype = fktype;
 			old_pathtype = findFkeyCast(pfeqop_right, old_fktype,
 										&old_castfunc);
@@ -8963,7 +8973,8 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 	ColumnDef  *def = (ColumnDef *) cmd->def;
 	TypeName   *typeName = def->typeName;
 	HeapTuple	heapTup;
-	Form_pg_attribute attTup;
+	Form_pg_attribute attTup,
+				attOldTup;
 	AttrNumber	attnum;
 	HeapTuple	typeTuple;
 	Form_pg_type tform;
@@ -8989,10 +9000,11 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 						colName, RelationGetRelationName(rel))));
 	attTup = (Form_pg_attribute) GETSTRUCT(heapTup);
 	attnum = attTup->attnum;
+	attOldTup = TupleDescAttr(tab->oldDesc, attnum - 1);
 
 	/* Check for multiple ALTER TYPE on same column --- can't cope */
-	if (attTup->atttypid != tab->oldDesc->attrs[attnum - 1]->atttypid ||
-		attTup->atttypmod != tab->oldDesc->attrs[attnum - 1]->atttypmod)
+	if (attTup->atttypid != attOldTup->atttypid ||
+		attTup->atttypmod != attOldTup->atttypmod)
 		ereport(ERROR,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("cannot alter type of column \"%s\" twice",
@@ -11209,7 +11221,8 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
 
 	for (parent_attno = 1; parent_attno <= parent_natts; parent_attno++)
 	{
-		Form_pg_attribute attribute = tupleDesc->attrs[parent_attno - 1];
+		Form_pg_attribute attribute = TupleDescAttr(tupleDesc,
+													parent_attno - 1);
 		char	   *attributeName = NameStr(attribute->attname);
 
 		/* Ignore dropped columns in the parent. */
@@ -11822,7 +11835,7 @@ ATExecAddOf(Relation rel, const TypeName *ofTypename, LOCKMODE lockmode)
 				   *table_attname;
 
 		/* Get the next non-dropped type attribute. */
-		type_attr = typeTupleDesc->attrs[type_attno - 1];
+		type_attr = TupleDescAttr(typeTupleDesc, type_attno - 1);
 		if (type_attr->attisdropped)
 			continue;
 		type_attname = NameStr(type_attr->attname);
@@ -11835,7 +11848,8 @@ ATExecAddOf(Relation rel, const TypeName *ofTypename, LOCKMODE lockmode)
 						(errcode(ERRCODE_DATATYPE_MISMATCH),
 						 errmsg("table is missing column \"%s\"",
 								type_attname)));
-			table_attr = tableTupleDesc->attrs[table_attno++ - 1];
+			table_attr = TupleDescAttr(tableTupleDesc, table_attno - 1);
+			table_attno++;
 		} while (table_attr->attisdropped);
 		table_attname = NameStr(table_attr->attname);
 
@@ -11860,7 +11874,8 @@ ATExecAddOf(Relation rel, const TypeName *ofTypename, LOCKMODE lockmode)
 	/* Any remaining columns at the end of the table had better be dropped. */
 	for (; table_attno <= tableTupleDesc->natts; table_attno++)
 	{
-		Form_pg_attribute table_attr = tableTupleDesc->attrs[table_attno - 1];
+		Form_pg_attribute table_attr = TupleDescAttr(tableTupleDesc,
+													 table_attno - 1);
 
 		if (!table_attr->attisdropped)
 			ereport(ERROR,
@@ -12147,7 +12162,7 @@ ATExecReplicaIdentity(Relation rel, ReplicaIdentityStmt *stmt, LOCKMODE lockmode
 					 errmsg("index \"%s\" cannot be used as replica identity because column %d is a system column",
 							RelationGetRelationName(indexRel), attno)));
 
-		attr = rel->rd_att->attrs[attno - 1];
+		attr = TupleDescAttr(rel->rd_att, attno - 1);
 		if (!attr->attnotnull)
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -13451,7 +13466,7 @@ PartConstraintImpliedByRelConstraint(Relation scanrel,
 
 		for (i = 1; i <= natts; i++)
 		{
-			Form_pg_attribute att = scanrel->rd_att->attrs[i - 1];
+			Form_pg_attribute att = TupleDescAttr(scanrel->rd_att, i - 1);
 
 			if (att->attnotnull && !att->attisdropped)
 			{
@@ -13733,7 +13748,7 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 	natts = tupleDesc->natts;
 	for (attno = 1; attno <= natts; attno++)
 	{
-		Form_pg_attribute attribute = tupleDesc->attrs[attno - 1];
+		Form_pg_attribute attribute = TupleDescAttr(tupleDesc, attno - 1);
 		char	   *attributeName = NameStr(attribute->attname);
 
 		/* Ignore dropped */
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 29ac5d569d724262383dfbd76b35a8977a56c619..7ed16aeff460eba0c90224d6e5c3f1c1fdbc2f1a 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -2324,6 +2324,7 @@ AlterDomainNotNull(List *names, bool notNull)
 				for (i = 0; i < rtc->natts; i++)
 				{
 					int			attnum = rtc->atts[i];
+					Form_pg_attribute attr = TupleDescAttr(tupdesc, attnum - 1);
 
 					if (heap_attisnull(tuple, attnum))
 					{
@@ -2338,7 +2339,7 @@ AlterDomainNotNull(List *names, bool notNull)
 						ereport(ERROR,
 								(errcode(ERRCODE_NOT_NULL_VIOLATION),
 								 errmsg("column \"%s\" of table \"%s\" contains null values",
-										NameStr(tupdesc->attrs[attnum - 1]->attname),
+										NameStr(attr->attname),
 										RelationGetRelationName(testrel)),
 								 errtablecol(testrel, attnum)));
 					}
@@ -2722,6 +2723,7 @@ validateDomainConstraint(Oid domainoid, char *ccbin)
 				Datum		d;
 				bool		isNull;
 				Datum		conResult;
+				Form_pg_attribute attr = TupleDescAttr(tupdesc, attnum - 1);
 
 				d = heap_getattr(tuple, attnum, tupdesc, &isNull);
 
@@ -2745,7 +2747,7 @@ validateDomainConstraint(Oid domainoid, char *ccbin)
 					ereport(ERROR,
 							(errcode(ERRCODE_CHECK_VIOLATION),
 							 errmsg("column \"%s\" of table \"%s\" contains values that violate the new constraint",
-									NameStr(tupdesc->attrs[attnum - 1]->attname),
+									NameStr(attr->attname),
 									RelationGetRelationName(testrel)),
 							 errtablecol(testrel, attnum)));
 				}
@@ -2930,7 +2932,7 @@ get_rels_with_domain(Oid domainOid, LOCKMODE lockmode)
 		 */
 		if (pg_depend->objsubid > RelationGetNumberOfAttributes(rtc->rel))
 			continue;
-		pg_att = rtc->rel->rd_att->attrs[pg_depend->objsubid - 1];
+		pg_att = TupleDescAttr(rtc->rel->rd_att, pg_depend->objsubid - 1);
 		if (pg_att->attisdropped || pg_att->atttypid != domainOid)
 			continue;
 
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index f25a5658d680743da2f466e06428b9dea489eff1..076e2a3a40212e5db9fd22474a65d104a0bcc369 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -283,8 +283,8 @@ checkViewTupleDesc(TupleDesc newdesc, TupleDesc olddesc)
 
 	for (i = 0; i < olddesc->natts; i++)
 	{
-		Form_pg_attribute newattr = newdesc->attrs[i];
-		Form_pg_attribute oldattr = olddesc->attrs[i];
+		Form_pg_attribute newattr = TupleDescAttr(newdesc, i);
+		Form_pg_attribute oldattr = TupleDescAttr(olddesc, i);
 
 		/* XXX msg not right, but we don't support DROP COL on view anyway */
 		if (newattr->attisdropped != oldattr->attisdropped)
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index 7496189fabf25f60a6e3fd98937ef9dd70a95c09..be9d23bc323ba208f16effb7429c4fcddca931af 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -347,7 +347,7 @@ ExecBuildProjectionInfo(List *targetList,
 				isSafeVar = true;	/* can't check, just assume OK */
 			else if (attnum <= inputDesc->natts)
 			{
-				Form_pg_attribute attr = inputDesc->attrs[attnum - 1];
+				Form_pg_attribute attr = TupleDescAttr(inputDesc, attnum - 1);
 
 				/*
 				 * If user attribute is dropped or has a type mismatch, don't
@@ -1492,7 +1492,6 @@ ExecInitExprRec(Expr *node, PlanState *parent, ExprState *state,
 				RowExpr    *rowexpr = (RowExpr *) node;
 				int			nelems = list_length(rowexpr->args);
 				TupleDesc	tupdesc;
-				Form_pg_attribute *attrs;
 				int			i;
 				ListCell   *l;
 
@@ -1539,13 +1538,13 @@ ExecInitExprRec(Expr *node, PlanState *parent, ExprState *state,
 				memset(scratch.d.row.elemnulls, true, sizeof(bool) * nelems);
 
 				/* Set up evaluation, skipping any deleted columns */
-				attrs = tupdesc->attrs;
 				i = 0;
 				foreach(l, rowexpr->args)
 				{
+					Form_pg_attribute att = TupleDescAttr(tupdesc, i);
 					Expr	   *e = (Expr *) lfirst(l);
 
-					if (!attrs[i]->attisdropped)
+					if (!att->attisdropped)
 					{
 						/*
 						 * Guard against ALTER COLUMN TYPE on rowtype since
@@ -1553,12 +1552,12 @@ ExecInitExprRec(Expr *node, PlanState *parent, ExprState *state,
 						 * typmod too?	Not sure we can be sure it'll be the
 						 * same.
 						 */
-						if (exprType((Node *) e) != attrs[i]->atttypid)
+						if (exprType((Node *) e) != att->atttypid)
 							ereport(ERROR,
 									(errcode(ERRCODE_DATATYPE_MISMATCH),
 									 errmsg("ROW() column has type %s instead of type %s",
 											format_type_be(exprType((Node *) e)),
-											format_type_be(attrs[i]->atttypid))));
+											format_type_be(att->atttypid))));
 					}
 					else
 					{
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index f2a52f62135bfcb218a99d1bd552a0a97d9cc1b0..83e04471e474c8726555c5904c475525540433e2 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -1553,7 +1553,7 @@ CheckVarSlotCompatibility(TupleTableSlot *slot, int attnum, Oid vartype)
 			elog(ERROR, "attribute number %d exceeds number of columns %d",
 				 attnum, slot_tupdesc->natts);
 
-		attr = slot_tupdesc->attrs[attnum - 1];
+		attr = TupleDescAttr(slot_tupdesc, attnum - 1);
 
 		if (attr->attisdropped)
 			ereport(ERROR,
@@ -2081,7 +2081,7 @@ ExecEvalRowNullInt(ExprState *state, ExprEvalStep *op,
 	for (att = 1; att <= tupDesc->natts; att++)
 	{
 		/* ignore dropped columns */
-		if (tupDesc->attrs[att - 1]->attisdropped)
+		if (TupleDescAttr(tupDesc, att - 1)->attisdropped)
 			continue;
 		if (heap_attisnull(&tmptup, att))
 		{
@@ -2494,7 +2494,7 @@ ExecEvalFieldSelect(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
 	if (fieldnum > tupDesc->natts)	/* should never happen */
 		elog(ERROR, "attribute number %d exceeds number of columns %d",
 			 fieldnum, tupDesc->natts);
-	attr = tupDesc->attrs[fieldnum - 1];
+	attr = TupleDescAttr(tupDesc, fieldnum - 1);
 
 	/* Check for dropped column, and force a NULL result if so */
 	if (attr->attisdropped)
@@ -3441,8 +3441,8 @@ ExecEvalWholeRowVar(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
 
 			for (i = 0; i < var_tupdesc->natts; i++)
 			{
-				Form_pg_attribute vattr = var_tupdesc->attrs[i];
-				Form_pg_attribute sattr = slot_tupdesc->attrs[i];
+				Form_pg_attribute vattr = TupleDescAttr(var_tupdesc, i);
+				Form_pg_attribute sattr = TupleDescAttr(slot_tupdesc, i);
 
 				if (vattr->atttypid == sattr->atttypid)
 					continue;	/* no worries */
@@ -3540,8 +3540,8 @@ ExecEvalWholeRowVar(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
 
 		for (i = 0; i < var_tupdesc->natts; i++)
 		{
-			Form_pg_attribute vattr = var_tupdesc->attrs[i];
-			Form_pg_attribute sattr = tupleDesc->attrs[i];
+			Form_pg_attribute vattr = TupleDescAttr(var_tupdesc, i);
+			Form_pg_attribute sattr = TupleDescAttr(tupleDesc, i);
 
 			if (!vattr->attisdropped)
 				continue;		/* already checked non-dropped cols */
diff --git a/src/backend/executor/execJunk.c b/src/backend/executor/execJunk.c
index a422327c88946b3563cacecc435b8a6d650d49d0..7fcd940fdb85cd195ce4aa97452874203e1b5f70 100644
--- a/src/backend/executor/execJunk.c
+++ b/src/backend/executor/execJunk.c
@@ -168,7 +168,7 @@ ExecInitJunkFilterConversion(List *targetList,
 		t = list_head(targetList);
 		for (i = 0; i < cleanLength; i++)
 		{
-			if (cleanTupType->attrs[i]->attisdropped)
+			if (TupleDescAttr(cleanTupType, i)->attisdropped)
 				continue;		/* map entry is already zero */
 			for (;;)
 			{
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 4582a3caa00de7f6c67085cb8c0ac7ede8dcc736..2946a0edee3b8f51d0a9794b758a765514e53abb 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1950,8 +1950,9 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 
 		for (attrChk = 1; attrChk <= natts; attrChk++)
 		{
-			if (tupdesc->attrs[attrChk - 1]->attnotnull &&
-				slot_attisnull(slot, attrChk))
+			Form_pg_attribute att = TupleDescAttr(tupdesc, attrChk - 1);
+
+			if (att->attnotnull && slot_attisnull(slot, attrChk))
 			{
 				char	   *val_desc;
 				Relation	orig_rel = rel;
@@ -1994,7 +1995,7 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 				ereport(ERROR,
 						(errcode(ERRCODE_NOT_NULL_VIOLATION),
 						 errmsg("null value in column \"%s\" violates not-null constraint",
-								NameStr(orig_tupdesc->attrs[attrChk - 1]->attname)),
+								NameStr(att->attname)),
 						 val_desc ? errdetail("Failing row contains %s.", val_desc) : 0,
 						 errtablecol(orig_rel, attrChk)));
 			}
@@ -2261,9 +2262,10 @@ ExecBuildSlotValueDescription(Oid reloid,
 		bool		column_perm = false;
 		char	   *val;
 		int			vallen;
+		Form_pg_attribute att = TupleDescAttr(tupdesc, i);
 
 		/* ignore dropped columns */
-		if (tupdesc->attrs[i]->attisdropped)
+		if (att->attisdropped)
 			continue;
 
 		if (!table_perm)
@@ -2274,9 +2276,9 @@ ExecBuildSlotValueDescription(Oid reloid,
 			 * for the column.  If not, omit this column from the error
 			 * message.
 			 */
-			aclresult = pg_attribute_aclcheck(reloid, tupdesc->attrs[i]->attnum,
+			aclresult = pg_attribute_aclcheck(reloid, att->attnum,
 											  GetUserId(), ACL_SELECT);
-			if (bms_is_member(tupdesc->attrs[i]->attnum - FirstLowInvalidHeapAttributeNumber,
+			if (bms_is_member(att->attnum - FirstLowInvalidHeapAttributeNumber,
 							  modifiedCols) || aclresult == ACLCHECK_OK)
 			{
 				column_perm = any_perm = true;
@@ -2286,7 +2288,7 @@ ExecBuildSlotValueDescription(Oid reloid,
 				else
 					write_comma_collist = true;
 
-				appendStringInfoString(&collist, NameStr(tupdesc->attrs[i]->attname));
+				appendStringInfoString(&collist, NameStr(att->attname));
 			}
 		}
 
@@ -2299,7 +2301,7 @@ ExecBuildSlotValueDescription(Oid reloid,
 				Oid			foutoid;
 				bool		typisvarlena;
 
-				getTypeOutputInfo(tupdesc->attrs[i]->atttypid,
+				getTypeOutputInfo(att->atttypid,
 								  &foutoid, &typisvarlena);
 				val = OidOutputFunctionCall(foutoid, slot->tts_values[i]);
 			}
diff --git a/src/backend/executor/execReplication.c b/src/backend/executor/execReplication.c
index 3819de28ad9f1b98eaffba84552b8c28e7bcdaea..fbb810851243d61d5f3f71e5b94d7e5e218e95d9 100644
--- a/src/backend/executor/execReplication.c
+++ b/src/backend/executor/execReplication.c
@@ -247,7 +247,7 @@ tuple_equals_slot(TupleDesc desc, HeapTuple tup, TupleTableSlot *slot)
 		if (isnull[attrnum])
 			continue;
 
-		att = desc->attrs[attrnum];
+		att = TupleDescAttr(desc, attrnum);
 
 		typentry = lookup_type_cache(att->atttypid, TYPECACHE_EQ_OPR_FINFO);
 		if (!OidIsValid(typentry->eq_opr_finfo.fn_oid))
diff --git a/src/backend/executor/execSRF.c b/src/backend/executor/execSRF.c
index 138e86ac674b87997c65dcb8a2986052f6c27d58..8bc90a6c7e83003070d28c991e007ce0519687ad 100644
--- a/src/backend/executor/execSRF.c
+++ b/src/backend/executor/execSRF.c
@@ -903,8 +903,8 @@ tupledesc_match(TupleDesc dst_tupdesc, TupleDesc src_tupdesc)
 
 	for (i = 0; i < dst_tupdesc->natts; i++)
 	{
-		Form_pg_attribute dattr = dst_tupdesc->attrs[i];
-		Form_pg_attribute sattr = src_tupdesc->attrs[i];
+		Form_pg_attribute dattr = TupleDescAttr(dst_tupdesc, i);
+		Form_pg_attribute sattr = TupleDescAttr(src_tupdesc, i);
 
 		if (IsBinaryCoercible(sattr->atttypid, dattr->atttypid))
 			continue;			/* no worries */
diff --git a/src/backend/executor/execScan.c b/src/backend/executor/execScan.c
index 4f131b3ee0df60619e1e7b332470f875092b0731..47a34a044a76b6c174c6432116c7385b0190b42f 100644
--- a/src/backend/executor/execScan.c
+++ b/src/backend/executor/execScan.c
@@ -269,7 +269,7 @@ tlist_matches_tupdesc(PlanState *ps, List *tlist, Index varno, TupleDesc tupdesc
 	/* Check the tlist attributes */
 	for (attrno = 1; attrno <= numattrs; attrno++)
 	{
-		Form_pg_attribute att_tup = tupdesc->attrs[attrno - 1];
+		Form_pg_attribute att_tup = TupleDescAttr(tupdesc, attrno - 1);
 		Var		   *var;
 
 		if (tlist_item == NULL)
diff --git a/src/backend/executor/execTuples.c b/src/backend/executor/execTuples.c
index 7ae70a877a0c306785154c1403b9b8fd3c32ae3f..31f814c0f072b49d6fea4fdf70acc5fc5c56e3b4 100644
--- a/src/backend/executor/execTuples.c
+++ b/src/backend/executor/execTuples.c
@@ -997,7 +997,8 @@ ExecTypeSetColNames(TupleDesc typeInfo, List *namesList)
 		/* Guard against too-long names list */
 		if (colno >= typeInfo->natts)
 			break;
-		attr = typeInfo->attrs[colno++];
+		attr = TupleDescAttr(typeInfo, colno);
+		colno++;
 
 		/* Ignore empty aliases (these must be for dropped columns) */
 		if (cname[0] == '\0')
@@ -1090,13 +1091,15 @@ TupleDescGetAttInMetadata(TupleDesc tupdesc)
 
 	for (i = 0; i < natts; i++)
 	{
+		Form_pg_attribute att = TupleDescAttr(tupdesc, i);
+
 		/* Ignore dropped attributes */
-		if (!tupdesc->attrs[i]->attisdropped)
+		if (!att->attisdropped)
 		{
-			atttypeid = tupdesc->attrs[i]->atttypid;
+			atttypeid = att->atttypid;
 			getTypeInputInfo(atttypeid, &attinfuncid, &attioparams[i]);
 			fmgr_info(attinfuncid, &attinfuncinfo[i]);
-			atttypmods[i] = tupdesc->attrs[i]->atttypmod;
+			atttypmods[i] = att->atttypmod;
 		}
 	}
 	attinmeta->attinfuncs = attinfuncinfo;
@@ -1127,7 +1130,7 @@ BuildTupleFromCStrings(AttInMetadata *attinmeta, char **values)
 	/* Call the "in" function for each non-dropped attribute */
 	for (i = 0; i < natts; i++)
 	{
-		if (!tupdesc->attrs[i]->attisdropped)
+		if (!TupleDescAttr(tupdesc, i)->attisdropped)
 		{
 			/* Non-dropped attributes */
 			dvalues[i] = InputFunctionCall(&attinmeta->attinfuncs[i],
diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c
index c3988468795e2920010ad072eac8c34e4417400f..95283939768fee60d5103d7e85341ed0aa4eea8d 100644
--- a/src/backend/executor/execUtils.c
+++ b/src/backend/executor/execUtils.c
@@ -917,9 +917,11 @@ GetAttributeByName(HeapTupleHeader tuple, const char *attname, bool *isNull)
 	attrno = InvalidAttrNumber;
 	for (i = 0; i < tupDesc->natts; i++)
 	{
-		if (namestrcmp(&(tupDesc->attrs[i]->attname), attname) == 0)
+		Form_pg_attribute att = TupleDescAttr(tupDesc, i);
+
+		if (namestrcmp(&(att->attname), attname) == 0)
 		{
-			attrno = tupDesc->attrs[i]->attnum;
+			attrno = att->attnum;
 			break;
 		}
 	}
diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c
index 3630f5d96689d7dd1168596617c58ccee60d14ac..b7ac5f7432dcc61a4e3726d75402cb68de99aa3a 100644
--- a/src/backend/executor/functions.c
+++ b/src/backend/executor/functions.c
@@ -1759,7 +1759,7 @@ check_sql_fn_retval(Oid func_id, Oid rettype, List *queryTreeList,
 							 errmsg("return type mismatch in function declared to return %s",
 									format_type_be(rettype)),
 							 errdetail("Final statement returns too many columns.")));
-				attr = tupdesc->attrs[colindex - 1];
+				attr = TupleDescAttr(tupdesc, colindex - 1);
 				if (attr->attisdropped && modifyTargetList)
 				{
 					Expr	   *null_expr;
@@ -1816,7 +1816,7 @@ check_sql_fn_retval(Oid func_id, Oid rettype, List *queryTreeList,
 		/* remaining columns in tupdesc had better all be dropped */
 		for (colindex++; colindex <= tupnatts; colindex++)
 		{
-			if (!tupdesc->attrs[colindex - 1]->attisdropped)
+			if (!TupleDescAttr(tupdesc, colindex - 1)->attisdropped)
 				ereport(ERROR,
 						(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
 						 errmsg("return type mismatch in function declared to return %s",
diff --git a/src/backend/executor/nodeAgg.c b/src/backend/executor/nodeAgg.c
index 6a26773a49f248e4c37f8cfa8e8f6545da773025..0ae5873868b9a1663eb722f16738811ecf6a9768 100644
--- a/src/backend/executor/nodeAgg.c
+++ b/src/backend/executor/nodeAgg.c
@@ -724,12 +724,16 @@ initialize_aggregate(AggState *aggstate, AggStatePerTrans pertrans,
 		 * process_ordered_aggregate_single.)
 		 */
 		if (pertrans->numInputs == 1)
+		{
+			Form_pg_attribute attr = TupleDescAttr(pertrans->sortdesc, 0);
+
 			pertrans->sortstates[aggstate->current_set] =
-				tuplesort_begin_datum(pertrans->sortdesc->attrs[0]->atttypid,
+				tuplesort_begin_datum(attr->atttypid,
 									  pertrans->sortOperators[0],
 									  pertrans->sortCollations[0],
 									  pertrans->sortNullsFirst[0],
 									  work_mem, false);
+		}
 		else
 			pertrans->sortstates[aggstate->current_set] =
 				tuplesort_begin_heap(pertrans->sortdesc,
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 70a6b847a0ead693e8fc4c0c60b296b78d5dc730..e12721a9b6ae56a0615062a371cd00ef29d280bd 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -95,7 +95,8 @@ ExecCheckPlanOutput(Relation resultRel, List *targetList)
 					(errcode(ERRCODE_DATATYPE_MISMATCH),
 					 errmsg("table row type and query-specified row type do not match"),
 					 errdetail("Query has too many columns.")));
-		attr = resultDesc->attrs[attno++];
+		attr = TupleDescAttr(resultDesc, attno);
+		attno++;
 
 		if (!attr->attisdropped)
 		{
diff --git a/src/backend/executor/nodeSubplan.c b/src/backend/executor/nodeSubplan.c
index fe10e809dfb8859fea0da273f22d5b7df130f11a..77ef6f3df13210e0fa8cfe23b9907c070c0579e5 100644
--- a/src/backend/executor/nodeSubplan.c
+++ b/src/backend/executor/nodeSubplan.c
@@ -360,7 +360,7 @@ ExecScanSubPlan(SubPlanState *node,
 
 			found = true;
 			/* stash away current value */
-			Assert(subplan->firstColType == tdesc->attrs[0]->atttypid);
+			Assert(subplan->firstColType == TupleDescAttr(tdesc, 0)->atttypid);
 			dvalue = slot_getattr(slot, 1, &disnull);
 			astate = accumArrayResultAny(astate, dvalue, disnull,
 										 subplan->firstColType, oldcontext);
@@ -992,7 +992,7 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
 
 			found = true;
 			/* stash away current value */
-			Assert(subplan->firstColType == tdesc->attrs[0]->atttypid);
+			Assert(subplan->firstColType == TupleDescAttr(tdesc, 0)->atttypid);
 			dvalue = slot_getattr(slot, 1, &disnull);
 			astate = accumArrayResultAny(astate, dvalue, disnull,
 										 subplan->firstColType, oldcontext);
diff --git a/src/backend/executor/nodeTableFuncscan.c b/src/backend/executor/nodeTableFuncscan.c
index b03d2ef76224d2f4258be3d9e77231f0b110f148..165fae8c83b46aeaf9a6b44b45a691e614d1c391 100644
--- a/src/backend/executor/nodeTableFuncscan.c
+++ b/src/backend/executor/nodeTableFuncscan.c
@@ -202,7 +202,7 @@ ExecInitTableFuncScan(TableFuncScan *node, EState *estate, int eflags)
 	{
 		Oid			in_funcid;
 
-		getTypeInputInfo(tupdesc->attrs[i]->atttypid,
+		getTypeInputInfo(TupleDescAttr(tupdesc, i)->atttypid,
 						 &in_funcid, &scanstate->typioparams[i]);
 		fmgr_info(in_funcid, &scanstate->in_functions[i]);
 	}
@@ -390,6 +390,7 @@ tfuncInitialize(TableFuncScanState *tstate, ExprContext *econtext, Datum doc)
 	foreach(lc1, tstate->colexprs)
 	{
 		char	   *colfilter;
+		Form_pg_attribute att = TupleDescAttr(tupdesc, colno);
 
 		if (colno != ordinalitycol)
 		{
@@ -403,11 +404,11 @@ tfuncInitialize(TableFuncScanState *tstate, ExprContext *econtext, Datum doc)
 							(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
 							 errmsg("column filter expression must not be null"),
 							 errdetail("Filter for column \"%s\" is null.",
-									   NameStr(tupdesc->attrs[colno]->attname))));
+									   NameStr(att->attname))));
 				colfilter = TextDatumGetCString(value);
 			}
 			else
-				colfilter = NameStr(tupdesc->attrs[colno]->attname);
+				colfilter = NameStr(att->attname);
 
 			routine->SetColumnFilter(tstate, colfilter, colno);
 		}
@@ -453,6 +454,8 @@ tfuncLoadRows(TableFuncScanState *tstate, ExprContext *econtext)
 		 */
 		for (colno = 0; colno < natts; colno++)
 		{
+			Form_pg_attribute att = TupleDescAttr(tupdesc, colno);
+
 			if (colno == ordinalitycol)
 			{
 				/* Fast path for ordinality column */
@@ -465,8 +468,8 @@ tfuncLoadRows(TableFuncScanState *tstate, ExprContext *econtext)
 
 				values[colno] = routine->GetValue(tstate,
 												  colno,
-												  tupdesc->attrs[colno]->atttypid,
-												  tupdesc->attrs[colno]->atttypmod,
+												  att->atttypid,
+												  att->atttypmod,
 												  &isnull);
 
 				/* No value?  Evaluate and apply the default, if any */
@@ -484,7 +487,7 @@ tfuncLoadRows(TableFuncScanState *tstate, ExprContext *econtext)
 					ereport(ERROR,
 							(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
 							 errmsg("null is not allowed in column \"%s\"",
-									NameStr(tupdesc->attrs[colno]->attname))));
+									NameStr(att->attname))));
 
 				nulls[colno] = isnull;
 			}
diff --git a/src/backend/executor/nodeValuesscan.c b/src/backend/executor/nodeValuesscan.c
index 6eacaed8bb8cf46c7863428f03331ea7e15a85f3..1a72bfe16002f99ab04195258cc1462a51b06aae 100644
--- a/src/backend/executor/nodeValuesscan.c
+++ b/src/backend/executor/nodeValuesscan.c
@@ -95,7 +95,6 @@ ValuesNext(ValuesScanState *node)
 		List	   *exprstatelist;
 		Datum	   *values;
 		bool	   *isnull;
-		Form_pg_attribute *att;
 		ListCell   *lc;
 		int			resind;
 
@@ -131,12 +130,13 @@ ValuesNext(ValuesScanState *node)
 		 */
 		values = slot->tts_values;
 		isnull = slot->tts_isnull;
-		att = slot->tts_tupleDescriptor->attrs;
 
 		resind = 0;
 		foreach(lc, exprstatelist)
 		{
 			ExprState  *estate = (ExprState *) lfirst(lc);
+			Form_pg_attribute attr = TupleDescAttr(slot->tts_tupleDescriptor,
+												   resind);
 
 			values[resind] = ExecEvalExpr(estate,
 										  econtext,
@@ -150,7 +150,7 @@ ValuesNext(ValuesScanState *node)
 			 */
 			values[resind] = MakeExpandedObjectReadOnly(values[resind],
 														isnull[resind],
-														att[resind]->attlen);
+														attr->attlen);
 
 			resind++;
 		}
diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c
index cd00a6d9f25e096fbd8f907cdc7d23830942dcd7..afe231fca9606662966b79e7612b1e86d4b5176a 100644
--- a/src/backend/executor/spi.c
+++ b/src/backend/executor/spi.c
@@ -765,8 +765,10 @@ SPI_fnumber(TupleDesc tupdesc, const char *fname)
 
 	for (res = 0; res < tupdesc->natts; res++)
 	{
-		if (namestrcmp(&tupdesc->attrs[res]->attname, fname) == 0 &&
-			!tupdesc->attrs[res]->attisdropped)
+		Form_pg_attribute attr = TupleDescAttr(tupdesc, res);
+
+		if (namestrcmp(&attr->attname, fname) == 0 &&
+			!attr->attisdropped)
 			return res + 1;
 	}
 
@@ -793,7 +795,7 @@ SPI_fname(TupleDesc tupdesc, int fnumber)
 	}
 
 	if (fnumber > 0)
-		att = tupdesc->attrs[fnumber - 1];
+		att = TupleDescAttr(tupdesc, fnumber - 1);
 	else
 		att = SystemAttributeDefinition(fnumber, true);
 
@@ -823,7 +825,7 @@ SPI_getvalue(HeapTuple tuple, TupleDesc tupdesc, int fnumber)
 		return NULL;
 
 	if (fnumber > 0)
-		typoid = tupdesc->attrs[fnumber - 1]->atttypid;
+		typoid = TupleDescAttr(tupdesc, fnumber - 1)->atttypid;
 	else
 		typoid = (SystemAttributeDefinition(fnumber, true))->atttypid;
 
@@ -865,7 +867,7 @@ SPI_gettype(TupleDesc tupdesc, int fnumber)
 	}
 
 	if (fnumber > 0)
-		typoid = tupdesc->attrs[fnumber - 1]->atttypid;
+		typoid = TupleDescAttr(tupdesc, fnumber - 1)->atttypid;
 	else
 		typoid = (SystemAttributeDefinition(fnumber, true))->atttypid;
 
@@ -901,7 +903,7 @@ SPI_gettypeid(TupleDesc tupdesc, int fnumber)
 	}
 
 	if (fnumber > 0)
-		return tupdesc->attrs[fnumber - 1]->atttypid;
+		return TupleDescAttr(tupdesc, fnumber - 1)->atttypid;
 	else
 		return (SystemAttributeDefinition(fnumber, true))->atttypid;
 }
diff --git a/src/backend/executor/tqueue.c b/src/backend/executor/tqueue.c
index a4cfe9685abceac121dfd85e8101ed5fe563655b..4c4fcf530d722b3e96bcbc6cf38e7a9210c4251b 100644
--- a/src/backend/executor/tqueue.c
+++ b/src/backend/executor/tqueue.c
@@ -551,7 +551,7 @@ TQSendRecordInfo(TQueueDestReceiver *tqueue, int32 typmod, TupleDesc tupledesc)
 	appendBinaryStringInfo(&buf, (char *) &tupledesc->tdhasoid, sizeof(bool));
 	for (i = 0; i < tupledesc->natts; i++)
 	{
-		appendBinaryStringInfo(&buf, (char *) tupledesc->attrs[i],
+		appendBinaryStringInfo(&buf, (char *) TupleDescAttr(tupledesc, i),
 							   sizeof(FormData_pg_attribute));
 	}
 
@@ -1253,7 +1253,7 @@ BuildFieldRemapInfo(TupleDesc tupledesc, MemoryContext mycontext)
 						   tupledesc->natts * sizeof(TupleRemapInfo *));
 	for (i = 0; i < tupledesc->natts; i++)
 	{
-		Form_pg_attribute attr = tupledesc->attrs[i];
+		Form_pg_attribute attr = TupleDescAttr(tupledesc, i);
 
 		if (attr->attisdropped)
 		{
diff --git a/src/backend/executor/tstoreReceiver.c b/src/backend/executor/tstoreReceiver.c
index eda38b1de1392d800e8f0c00b115a0c246e42e52..027fa72f107831c5b3e5566b670d1a4f8ba05363 100644
--- a/src/backend/executor/tstoreReceiver.c
+++ b/src/backend/executor/tstoreReceiver.c
@@ -49,7 +49,6 @@ tstoreStartupReceiver(DestReceiver *self, int operation, TupleDesc typeinfo)
 {
 	TStoreState *myState = (TStoreState *) self;
 	bool		needtoast = false;
-	Form_pg_attribute *attrs = typeinfo->attrs;
 	int			natts = typeinfo->natts;
 	int			i;
 
@@ -58,9 +57,11 @@ tstoreStartupReceiver(DestReceiver *self, int operation, TupleDesc typeinfo)
 	{
 		for (i = 0; i < natts; i++)
 		{
-			if (attrs[i]->attisdropped)
+			Form_pg_attribute attr = TupleDescAttr(typeinfo, i);
+
+			if (attr->attisdropped)
 				continue;
-			if (attrs[i]->attlen == -1)
+			if (attr->attlen == -1)
 			{
 				needtoast = true;
 				break;
@@ -109,7 +110,6 @@ tstoreReceiveSlot_detoast(TupleTableSlot *slot, DestReceiver *self)
 {
 	TStoreState *myState = (TStoreState *) self;
 	TupleDesc	typeinfo = slot->tts_tupleDescriptor;
-	Form_pg_attribute *attrs = typeinfo->attrs;
 	int			natts = typeinfo->natts;
 	int			nfree;
 	int			i;
@@ -127,10 +127,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);
 
-		if (!attrs[i]->attisdropped &&
-			attrs[i]->attlen == -1 &&
-			!slot->tts_isnull[i])
+		if (!attr->attisdropped && attr->attlen == -1 && !slot->tts_isnull[i])
 		{
 			if (VARATT_IS_EXTERNAL(DatumGetPointer(val)))
 			{
diff --git a/src/backend/optimizer/prep/preptlist.c b/src/backend/optimizer/prep/preptlist.c
index afc733f1835763f5ac2812371bf516d38077cb7c..9d75e8612ae73bce94c066fa54bea549ca85f05f 100644
--- a/src/backend/optimizer/prep/preptlist.c
+++ b/src/backend/optimizer/prep/preptlist.c
@@ -248,7 +248,7 @@ expand_targetlist(List *tlist, int command_type,
 
 	for (attrno = 1; attrno <= numattrs; attrno++)
 	{
-		Form_pg_attribute att_tup = rel->rd_att->attrs[attrno - 1];
+		Form_pg_attribute att_tup = TupleDescAttr(rel->rd_att, attrno - 1);
 		TargetEntry *new_tle = NULL;
 
 		if (tlist_item != NULL)
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index f43c3f300767d7af0ce84068cca18e0ceab661f5..e73c819901e86eb6ca58e85c7ae62ce7abe5c2ad 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -1648,7 +1648,7 @@ make_inh_translation_list(Relation oldrelation, Relation newrelation,
 		Oid			attcollation;
 		int			new_attno;
 
-		att = old_tupdesc->attrs[old_attno];
+		att = TupleDescAttr(old_tupdesc, old_attno);
 		if (att->attisdropped)
 		{
 			/* Just put NULL into this list entry */
@@ -1686,7 +1686,7 @@ make_inh_translation_list(Relation oldrelation, Relation newrelation,
 		 * notational device to include the assignment into the if-clause.
 		 */
 		if (old_attno < newnatts &&
-			(att = new_tupdesc->attrs[old_attno]) != NULL &&
+			(att = TupleDescAttr(new_tupdesc, old_attno)) != NULL &&
 			!att->attisdropped && att->attinhcount != 0 &&
 			strcmp(attname, NameStr(att->attname)) == 0)
 			new_attno = old_attno;
@@ -1694,7 +1694,7 @@ make_inh_translation_list(Relation oldrelation, Relation newrelation,
 		{
 			for (new_attno = 0; new_attno < newnatts; new_attno++)
 			{
-				att = new_tupdesc->attrs[new_attno];
+				att = TupleDescAttr(new_tupdesc, new_attno);
 				if (!att->attisdropped && att->attinhcount != 0 &&
 					strcmp(attname, NameStr(att->attname)) == 0)
 					break;
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 602d17dfb454140a1a0f8701e8f9334abb10d727..93add27dbe2f0cb72bcf74a30df8f2e175bcafef 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -2366,7 +2366,7 @@ rowtype_field_matches(Oid rowtypeid, int fieldnum,
 		ReleaseTupleDesc(tupdesc);
 		return false;
 	}
-	attr = tupdesc->attrs[fieldnum - 1];
+	attr = TupleDescAttr(tupdesc, fieldnum - 1);
 	if (attr->attisdropped ||
 		attr->atttypid != expectedtype ||
 		attr->atttypmod != expectedtypmod ||
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index dc0b0b070672a823a8b6cd5653dd9adc837d42a6..a1ebd4acc815df82daa5f644e008e2686fdb56da 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -1072,7 +1072,7 @@ get_rel_data_width(Relation rel, int32 *attr_widths)
 
 	for (i = 1; i <= RelationGetNumberOfAttributes(rel); i++)
 	{
-		Form_pg_attribute att = rel->rd_att->attrs[i - 1];
+		Form_pg_attribute att = TupleDescAttr(rel->rd_att, i - 1);
 		int32		item_width;
 
 		if (att->attisdropped)
@@ -1208,7 +1208,7 @@ get_relation_constraints(PlannerInfo *root,
 
 			for (i = 1; i <= natts; i++)
 			{
-				Form_pg_attribute att = relation->rd_att->attrs[i - 1];
+				Form_pg_attribute att = TupleDescAttr(relation->rd_att, i - 1);
 
 				if (att->attnotnull && !att->attisdropped)
 				{
@@ -1489,7 +1489,8 @@ build_physical_tlist(PlannerInfo *root, RelOptInfo *rel)
 			numattrs = RelationGetNumberOfAttributes(relation);
 			for (attrno = 1; attrno <= numattrs; attrno++)
 			{
-				Form_pg_attribute att_tup = relation->rd_att->attrs[attrno - 1];
+				Form_pg_attribute att_tup = TupleDescAttr(relation->rd_att,
+														  attrno - 1);
 
 				if (att_tup->attisdropped)
 				{
@@ -1609,7 +1610,7 @@ build_index_tlist(PlannerInfo *root, IndexOptInfo *index,
 				att_tup = SystemAttributeDefinition(indexkey,
 													heapRelation->rd_rel->relhasoids);
 			else
-				att_tup = heapRelation->rd_att->attrs[indexkey - 1];
+				att_tup = TupleDescAttr(heapRelation->rd_att, indexkey - 1);
 
 			indexvar = (Expr *) makeVar(varno,
 										indexkey,
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 4fb793cfbf025400c44b431f580f5046ca598bbd..757a4a8fd12cc68011d5c1d26645e47963c50b20 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -1050,7 +1050,7 @@ transformOnConflictClause(ParseState *pstate,
 		 */
 		for (attno = 0; attno < targetrel->rd_rel->relnatts; attno++)
 		{
-			Form_pg_attribute attr = targetrel->rd_att->attrs[attno];
+			Form_pg_attribute attr = TupleDescAttr(targetrel->rd_att, attno);
 			char	   *name;
 
 			if (attr->attisdropped)
diff --git a/src/backend/parser/parse_coerce.c b/src/backend/parser/parse_coerce.c
index 0bc7dba6a0e02b05e80135f6965f52bf1fa646ce..e95cee1ebfa85fd7b49562775fb86fa4ed1fa08b 100644
--- a/src/backend/parser/parse_coerce.c
+++ b/src/backend/parser/parse_coerce.c
@@ -982,9 +982,10 @@ coerce_record_to_complex(ParseState *pstate, Node *node,
 		Node	   *expr;
 		Node	   *cexpr;
 		Oid			exprtype;
+		Form_pg_attribute attr = TupleDescAttr(tupdesc, i);
 
 		/* Fill in NULLs for dropped columns in rowtype */
-		if (tupdesc->attrs[i]->attisdropped)
+		if (attr->attisdropped)
 		{
 			/*
 			 * can't use atttypid here, but it doesn't really matter what type
@@ -1008,8 +1009,8 @@ coerce_record_to_complex(ParseState *pstate, Node *node,
 
 		cexpr = coerce_to_target_type(pstate,
 									  expr, exprtype,
-									  tupdesc->attrs[i]->atttypid,
-									  tupdesc->attrs[i]->atttypmod,
+									  attr->atttypid,
+									  attr->atttypmod,
 									  ccontext,
 									  COERCE_IMPLICIT_CAST,
 									  -1);
@@ -1021,7 +1022,7 @@ coerce_record_to_complex(ParseState *pstate, Node *node,
 							format_type_be(targetTypeId)),
 					 errdetail("Cannot cast type %s to %s in column %d.",
 							   format_type_be(exprtype),
-							   format_type_be(tupdesc->attrs[i]->atttypid),
+							   format_type_be(attr->atttypid),
 							   ucolno),
 					 parser_coercion_errposition(pstate, location, expr)));
 		newargs = lappend(newargs, cexpr);
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
index 8487edaa958eb12e10344404368510866cfe2fed..2f2f2c7fb07bacb92ed2f15f9ec42df28688f468 100644
--- a/src/backend/parser/parse_func.c
+++ b/src/backend/parser/parse_func.c
@@ -1834,7 +1834,7 @@ ParseComplexProjection(ParseState *pstate, char *funcname, Node *first_arg,
 
 	for (i = 0; i < tupdesc->natts; i++)
 	{
-		Form_pg_attribute att = tupdesc->attrs[i];
+		Form_pg_attribute att = TupleDescAttr(tupdesc, i);
 
 		if (strcmp(funcname, NameStr(att->attname)) == 0 &&
 			!att->attisdropped)
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index 684a50d3df66af05baaae1cf7d753abb2b97b758..88b3e88a21fd4bed351ae7ed71f0a8ccf40b2869 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -1052,7 +1052,7 @@ buildRelationAliases(TupleDesc tupdesc, Alias *alias, Alias *eref)
 
 	for (varattno = 0; varattno < maxattrs; varattno++)
 	{
-		Form_pg_attribute attr = tupdesc->attrs[varattno];
+		Form_pg_attribute attr = TupleDescAttr(tupdesc, varattno);
 		Value	   *attrname;
 
 		if (attr->attisdropped)
@@ -2026,19 +2026,18 @@ addRangeTableEntryForENR(ParseState *pstate,
 	rte->colcollations = NIL;
 	for (attno = 1; attno <= tupdesc->natts; ++attno)
 	{
-		if (tupdesc->attrs[attno - 1]->atttypid == InvalidOid &&
-			!(tupdesc->attrs[attno - 1]->attisdropped))
+		Form_pg_attribute att = TupleDescAttr(tupdesc, attno - 1);
+
+		if (att->atttypid == InvalidOid &&
+			!(att->attisdropped))
 			elog(ERROR, "atttypid was invalid for column which has not been dropped from \"%s\"",
 				 rv->relname);
 		rte->coltypes =
-			lappend_oid(rte->coltypes,
-						tupdesc->attrs[attno - 1]->atttypid);
+			lappend_oid(rte->coltypes, att->atttypid);
 		rte->coltypmods =
-			lappend_int(rte->coltypmods,
-						tupdesc->attrs[attno - 1]->atttypmod);
+			lappend_int(rte->coltypmods, att->atttypmod);
 		rte->colcollations =
-			lappend_oid(rte->colcollations,
-						tupdesc->attrs[attno - 1]->attcollation);
+			lappend_oid(rte->colcollations, att->attcollation);
 	}
 
 	/*
@@ -2514,7 +2513,7 @@ expandTupleDesc(TupleDesc tupdesc, Alias *eref, int count, int offset,
 	Assert(count <= tupdesc->natts);
 	for (varattno = 0; varattno < count; varattno++)
 	{
-		Form_pg_attribute attr = tupdesc->attrs[varattno];
+		Form_pg_attribute attr = TupleDescAttr(tupdesc, varattno);
 
 		if (attr->attisdropped)
 		{
@@ -2749,7 +2748,7 @@ get_rte_attribute_type(RangeTblEntry *rte, AttrNumber attnum,
 
 							Assert(tupdesc);
 							Assert(attnum <= tupdesc->natts);
-							att_tup = tupdesc->attrs[attnum - 1];
+							att_tup = TupleDescAttr(tupdesc, attnum - 1);
 
 							/*
 							 * If dropped column, pretend it ain't there.  See
@@ -2953,7 +2952,8 @@ get_rte_attribute_is_dropped(RangeTblEntry *rte, AttrNumber attnum)
 
 							Assert(tupdesc);
 							Assert(attnum - atts_done <= tupdesc->natts);
-							att_tup = tupdesc->attrs[attnum - atts_done - 1];
+							att_tup = TupleDescAttr(tupdesc,
+													attnum - atts_done - 1);
 							return att_tup->attisdropped;
 						}
 						/* Otherwise, it can't have any dropped columns */
@@ -3042,7 +3042,7 @@ attnameAttNum(Relation rd, const char *attname, bool sysColOK)
 
 	for (i = 0; i < rd->rd_rel->relnatts; i++)
 	{
-		Form_pg_attribute att = rd->rd_att->attrs[i];
+		Form_pg_attribute att = TupleDescAttr(rd->rd_att, i);
 
 		if (namestrcmp(&(att->attname), attname) == 0 && !att->attisdropped)
 			return i + 1;
@@ -3102,7 +3102,7 @@ attnumAttName(Relation rd, int attid)
 	}
 	if (attid > rd->rd_att->natts)
 		elog(ERROR, "invalid attribute number %d", attid);
-	return &rd->rd_att->attrs[attid - 1]->attname;
+	return &TupleDescAttr(rd->rd_att, attid - 1)->attname;
 }
 
 /*
@@ -3124,7 +3124,7 @@ attnumTypeId(Relation rd, int attid)
 	}
 	if (attid > rd->rd_att->natts)
 		elog(ERROR, "invalid attribute number %d", attid);
-	return rd->rd_att->attrs[attid - 1]->atttypid;
+	return TupleDescAttr(rd->rd_att, attid - 1)->atttypid;
 }
 
 /*
@@ -3142,7 +3142,7 @@ attnumCollationId(Relation rd, int attid)
 	}
 	if (attid > rd->rd_att->natts)
 		elog(ERROR, "invalid attribute number %d", attid);
-	return rd->rd_att->attrs[attid - 1]->attcollation;
+	return TupleDescAttr(rd->rd_att, attid - 1)->attcollation;
 }
 
 /*
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 0a70539fb11c52ce03a51ab7f827c603e1ba3bfd..c3cb0357ca9077d823a1a81973ab7a696fb8ae57 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -484,8 +484,8 @@ transformAssignedExpr(ParseState *pstate,
 						colname),
 				 parser_errposition(pstate, location)));
 	attrtype = attnumTypeId(rd, attrno);
-	attrtypmod = rd->rd_att->attrs[attrno - 1]->atttypmod;
-	attrcollation = rd->rd_att->attrs[attrno - 1]->attcollation;
+	attrtypmod = TupleDescAttr(rd->rd_att, attrno - 1)->atttypmod;
+	attrcollation = TupleDescAttr(rd->rd_att, attrno - 1)->attcollation;
 
 	/*
 	 * If the expression is a DEFAULT placeholder, insert the attribute's
@@ -959,19 +959,21 @@ checkInsertTargets(ParseState *pstate, List *cols, List **attrnos)
 		/*
 		 * Generate default column list for INSERT.
 		 */
-		Form_pg_attribute *attr = pstate->p_target_relation->rd_att->attrs;
 		int			numcol = pstate->p_target_relation->rd_rel->relnatts;
 		int			i;
 
 		for (i = 0; i < numcol; i++)
 		{
 			ResTarget  *col;
+			Form_pg_attribute attr;
 
-			if (attr[i]->attisdropped)
+			attr = TupleDescAttr(pstate->p_target_relation->rd_att, i);
+
+			if (attr->attisdropped)
 				continue;
 
 			col = makeNode(ResTarget);
-			col->name = pstrdup(NameStr(attr[i]->attname));
+			col->name = pstrdup(NameStr(attr->attname));
 			col->indirection = NIL;
 			col->val = NULL;
 			col->location = -1;
@@ -1407,7 +1409,7 @@ ExpandRowReference(ParseState *pstate, Node *expr,
 	numAttrs = tupleDesc->natts;
 	for (i = 0; i < numAttrs; i++)
 	{
-		Form_pg_attribute att = tupleDesc->attrs[i];
+		Form_pg_attribute att = TupleDescAttr(tupleDesc, i);
 		FieldSelect *fselect;
 
 		if (att->attisdropped)
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 495ba3dffcb04580c529deb221bc95e9ef7cd7fb..20586797cc56316d4bfae6500bc77cadcc9a09e8 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -969,7 +969,8 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
 	for (parent_attno = 1; parent_attno <= tupleDesc->natts;
 		 parent_attno++)
 	{
-		Form_pg_attribute attribute = tupleDesc->attrs[parent_attno - 1];
+		Form_pg_attribute attribute = TupleDescAttr(tupleDesc,
+													parent_attno - 1);
 		char	   *attributeName = NameStr(attribute->attname);
 		ColumnDef  *def;
 
@@ -1219,7 +1220,7 @@ transformOfType(CreateStmtContext *cxt, TypeName *ofTypename)
 	tupdesc = lookup_rowtype_tupdesc(ofTypeId, -1);
 	for (i = 0; i < tupdesc->natts; i++)
 	{
-		Form_pg_attribute attr = tupdesc->attrs[i];
+		Form_pg_attribute attr = TupleDescAttr(tupdesc, i);
 		ColumnDef  *n;
 
 		if (attr->attisdropped)
@@ -1256,7 +1257,6 @@ generateClonedIndexStmt(CreateStmtContext *cxt, Relation source_idx,
 						const AttrNumber *attmap, int attmap_length)
 {
 	Oid			source_relid = RelationGetRelid(source_idx);
-	Form_pg_attribute *attrs = RelationGetDescr(source_idx)->attrs;
 	HeapTuple	ht_idxrel;
 	HeapTuple	ht_idx;
 	HeapTuple	ht_am;
@@ -1434,6 +1434,8 @@ generateClonedIndexStmt(CreateStmtContext *cxt, Relation source_idx,
 	{
 		IndexElem  *iparam;
 		AttrNumber	attnum = idxrec->indkey.values[keyno];
+		Form_pg_attribute attr = TupleDescAttr(RelationGetDescr(source_idx),
+											   keyno);
 		int16		opt = source_idx->rd_indoption[keyno];
 
 		iparam = makeNode(IndexElem);
@@ -1481,7 +1483,7 @@ generateClonedIndexStmt(CreateStmtContext *cxt, Relation source_idx,
 		}
 
 		/* Copy the original index column name */
-		iparam->indexcolname = pstrdup(NameStr(attrs[keyno]->attname));
+		iparam->indexcolname = pstrdup(NameStr(attr->attname));
 
 		/* Add the collation name, if non-default */
 		iparam->collation = get_collation(indcollation->values[keyno], keycoltype);
@@ -1921,7 +1923,7 @@ transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt)
 			if (attnum > 0)
 			{
 				Assert(attnum <= heap_rel->rd_att->natts);
-				attform = heap_rel->rd_att->attrs[attnum - 1];
+				attform = TupleDescAttr(heap_rel->rd_att, attnum - 1);
 			}
 			else
 				attform = SystemAttributeDefinition(attnum,
@@ -2040,7 +2042,8 @@ transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt)
 									inh->relname)));
 				for (count = 0; count < rel->rd_att->natts; count++)
 				{
-					Form_pg_attribute inhattr = rel->rd_att->attrs[count];
+					Form_pg_attribute inhattr = TupleDescAttr(rel->rd_att,
+															  count);
 					char	   *inhname = NameStr(inhattr->attname);
 
 					if (inhattr->attisdropped)
diff --git a/src/backend/replication/logical/proto.c b/src/backend/replication/logical/proto.c
index 94dfee0b24e61bf4085853f9deb965f12a3794f7..f19649b113c297bb91ace4fdfc41399899882079 100644
--- a/src/backend/replication/logical/proto.c
+++ b/src/backend/replication/logical/proto.c
@@ -398,7 +398,7 @@ logicalrep_write_tuple(StringInfo out, Relation rel, HeapTuple tuple)
 
 	for (i = 0; i < desc->natts; i++)
 	{
-		if (desc->attrs[i]->attisdropped)
+		if (TupleDescAttr(desc, i)->attisdropped)
 			continue;
 		nliveatts++;
 	}
@@ -415,7 +415,7 @@ logicalrep_write_tuple(StringInfo out, Relation rel, HeapTuple tuple)
 	{
 		HeapTuple	typtup;
 		Form_pg_type typclass;
-		Form_pg_attribute att = desc->attrs[i];
+		Form_pg_attribute att = TupleDescAttr(desc, i);
 		char	   *outputstr;
 
 		/* skip dropped columns */
@@ -518,7 +518,7 @@ logicalrep_write_attrs(StringInfo out, Relation rel)
 	/* send number of live attributes */
 	for (i = 0; i < desc->natts; i++)
 	{
-		if (desc->attrs[i]->attisdropped)
+		if (TupleDescAttr(desc, i)->attisdropped)
 			continue;
 		nliveatts++;
 	}
@@ -533,7 +533,7 @@ logicalrep_write_attrs(StringInfo out, Relation rel)
 	/* send the attributes */
 	for (i = 0; i < desc->natts; i++)
 	{
-		Form_pg_attribute att = desc->attrs[i];
+		Form_pg_attribute att = TupleDescAttr(desc, i);
 		uint8		flags = 0;
 
 		if (att->attisdropped)
diff --git a/src/backend/replication/logical/relation.c b/src/backend/replication/logical/relation.c
index a7ea16d714b02ed1f569d8e67ac37436bd193ac3..408143ae95622448c507ecdc6977586d3de86a81 100644
--- a/src/backend/replication/logical/relation.c
+++ b/src/backend/replication/logical/relation.c
@@ -278,15 +278,16 @@ logicalrep_rel_open(LogicalRepRelId remoteid, LOCKMODE lockmode)
 		for (i = 0; i < desc->natts; i++)
 		{
 			int			attnum;
+			Form_pg_attribute attr = TupleDescAttr(desc, i);
 
-			if (desc->attrs[i]->attisdropped)
+			if (attr->attisdropped)
 			{
 				entry->attrmap[i] = -1;
 				continue;
 			}
 
 			attnum = logicalrep_rel_att_by_name(remoterel,
-												NameStr(desc->attrs[i]->attname));
+												NameStr(attr->attname));
 
 			entry->attrmap[i] = attnum;
 			if (attnum >= 0)
diff --git a/src/backend/replication/logical/reorderbuffer.c b/src/backend/replication/logical/reorderbuffer.c
index 5567bee061da009a28d9487ca713894f64bede18..657bafae579fc9b1085b2070a1c8cbde04dc15f9 100644
--- a/src/backend/replication/logical/reorderbuffer.c
+++ b/src/backend/replication/logical/reorderbuffer.c
@@ -2800,7 +2800,7 @@ ReorderBufferToastReplace(ReorderBuffer *rb, ReorderBufferTXN *txn,
 
 	for (natt = 0; natt < desc->natts; natt++)
 	{
-		Form_pg_attribute attr = desc->attrs[natt];
+		Form_pg_attribute attr = TupleDescAttr(desc, natt);
 		ReorderBufferToastEnt *ent;
 		struct varlena *varlena;
 
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index 7c2df576457958697c23010086d02cbf781f9b03..041f3873b93b26b9d11898604fd7792ee11ef8f6 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -247,7 +247,7 @@ slot_fill_defaults(LogicalRepRelMapEntry *rel, EState *estate,
 	{
 		Expr	   *defexpr;
 
-		if (desc->attrs[attnum]->attisdropped)
+		if (TupleDescAttr(desc, attnum)->attisdropped)
 			continue;
 
 		if (rel->attrmap[attnum] >= 0)
@@ -323,7 +323,7 @@ slot_store_cstrings(TupleTableSlot *slot, LogicalRepRelMapEntry *rel,
 	/* Call the "in" function for each non-dropped attribute */
 	for (i = 0; i < natts; i++)
 	{
-		Form_pg_attribute att = slot->tts_tupleDescriptor->attrs[i];
+		Form_pg_attribute att = TupleDescAttr(slot->tts_tupleDescriptor, i);
 		int			remoteattnum = rel->attrmap[i];
 
 		if (!att->attisdropped && remoteattnum >= 0 &&
@@ -388,7 +388,7 @@ slot_modify_cstrings(TupleTableSlot *slot, LogicalRepRelMapEntry *rel,
 	/* Call the "in" function for each replaced attribute */
 	for (i = 0; i < natts; i++)
 	{
-		Form_pg_attribute att = slot->tts_tupleDescriptor->attrs[i];
+		Form_pg_attribute att = TupleDescAttr(slot->tts_tupleDescriptor, i);
 		int			remoteattnum = rel->attrmap[i];
 
 		if (remoteattnum >= 0 && !replaces[remoteattnum])
diff --git a/src/backend/replication/pgoutput/pgoutput.c b/src/backend/replication/pgoutput/pgoutput.c
index 370b74f23278fcf4c0558ed41985e1c52d8ff870..67c1d3b246a9515fc3e05ccd7aeaf8eb60555e5f 100644
--- a/src/backend/replication/pgoutput/pgoutput.c
+++ b/src/backend/replication/pgoutput/pgoutput.c
@@ -303,7 +303,7 @@ pgoutput_change(LogicalDecodingContext *ctx, ReorderBufferTXN *txn,
 		 */
 		for (i = 0; i < desc->natts; i++)
 		{
-			Form_pg_attribute att = desc->attrs[i];
+			Form_pg_attribute att = TupleDescAttr(desc, i);
 
 			if (att->attisdropped)
 				continue;
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index 9e6865bef6454dd6166e6a8aef5b9a24693d5870..d03984a2debcda6c82734b622ec43668d03f8850 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -676,7 +676,7 @@ checkRuleResultList(List *targetList, TupleDesc resultDesc, bool isSelect,
 					 errmsg("SELECT rule's target list has too many entries") :
 					 errmsg("RETURNING list has too many entries")));
 
-		attr = resultDesc->attrs[i - 1];
+		attr = TupleDescAttr(resultDesc, i - 1);
 		attname = NameStr(attr->attname);
 
 		/*
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index 6b79c6979550067898d06bb268cd253ef91ef5e2..ef52dd5b955eaa2d683d5082ea67d687c0c41cc5 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -751,7 +751,7 @@ rewriteTargetListIU(List *targetList,
 			attrno = old_tle->resno;
 			if (attrno < 1 || attrno > numattrs)
 				elog(ERROR, "bogus resno %d in targetlist", attrno);
-			att_tup = target_relation->rd_att->attrs[attrno - 1];
+			att_tup = TupleDescAttr(target_relation->rd_att, attrno - 1);
 
 			/* put attrno into attrno_list even if it's dropped */
 			if (attrno_list)
@@ -794,7 +794,7 @@ rewriteTargetListIU(List *targetList,
 		TargetEntry *new_tle = new_tles[attrno - 1];
 		bool		apply_default;
 
-		att_tup = target_relation->rd_att->attrs[attrno - 1];
+		att_tup = TupleDescAttr(target_relation->rd_att, attrno - 1);
 
 		/* We can (and must) ignore deleted attributes */
 		if (att_tup->attisdropped)
@@ -1112,7 +1112,7 @@ Node *
 build_column_default(Relation rel, int attrno)
 {
 	TupleDesc	rd_att = rel->rd_att;
-	Form_pg_attribute att_tup = rd_att->attrs[attrno - 1];
+	Form_pg_attribute att_tup = TupleDescAttr(rd_att, attrno - 1);
 	Oid			atttype = att_tup->atttypid;
 	int32		atttypmod = att_tup->atttypmod;
 	Node	   *expr = NULL;
@@ -1247,7 +1247,7 @@ rewriteValuesRTE(RangeTblEntry *rte, Relation target_relation, List *attrnos)
 				Form_pg_attribute att_tup;
 				Node	   *new_expr;
 
-				att_tup = target_relation->rd_att->attrs[attrno - 1];
+				att_tup = TupleDescAttr(target_relation->rd_att, attrno - 1);
 
 				if (!att_tup->attisdropped)
 					new_expr = build_column_default(target_relation, attrno);
diff --git a/src/backend/utils/adt/json.c b/src/backend/utils/adt/json.c
index 4dd7d977e8ac1a2b03f4e9e973206f92916b6df0..1ddb42b4d08c5ddf7aca75f13603e2c07b3def1b 100644
--- a/src/backend/utils/adt/json.c
+++ b/src/backend/utils/adt/json.c
@@ -1714,15 +1714,16 @@ composite_to_json(Datum composite, StringInfo result, bool use_line_feeds)
 		char	   *attname;
 		JsonTypeCategory tcategory;
 		Oid			outfuncoid;
+		Form_pg_attribute att = TupleDescAttr(tupdesc, i);
 
-		if (tupdesc->attrs[i]->attisdropped)
+		if (att->attisdropped)
 			continue;
 
 		if (needsep)
 			appendStringInfoString(result, sep);
 		needsep = true;
 
-		attname = NameStr(tupdesc->attrs[i]->attname);
+		attname = NameStr(att->attname);
 		escape_json(result, attname);
 		appendStringInfoChar(result, ':');
 
@@ -1734,8 +1735,7 @@ composite_to_json(Datum composite, StringInfo result, bool use_line_feeds)
 			outfuncoid = InvalidOid;
 		}
 		else
-			json_categorize_type(tupdesc->attrs[i]->atttypid,
-								 &tcategory, &outfuncoid);
+			json_categorize_type(att->atttypid, &tcategory, &outfuncoid);
 
 		datum_to_json(val, isnull, result, tcategory, outfuncoid, false);
 	}
diff --git a/src/backend/utils/adt/jsonb.c b/src/backend/utils/adt/jsonb.c
index 49f41f9f99982e43d66e7ca2e008be99948d1030..1eb7f3d6f9d52192687aeeedc07635ea76da72bc 100644
--- a/src/backend/utils/adt/jsonb.c
+++ b/src/backend/utils/adt/jsonb.c
@@ -1075,11 +1075,12 @@ composite_to_jsonb(Datum composite, JsonbInState *result)
 		JsonbTypeCategory tcategory;
 		Oid			outfuncoid;
 		JsonbValue	v;
+		Form_pg_attribute att = TupleDescAttr(tupdesc, i);
 
-		if (tupdesc->attrs[i]->attisdropped)
+		if (att->attisdropped)
 			continue;
 
-		attname = NameStr(tupdesc->attrs[i]->attname);
+		attname = NameStr(att->attname);
 
 		v.type = jbvString;
 		/* don't need checkStringLen here - can't exceed maximum name length */
@@ -1096,8 +1097,7 @@ composite_to_jsonb(Datum composite, JsonbInState *result)
 			outfuncoid = InvalidOid;
 		}
 		else
-			jsonb_categorize_type(tupdesc->attrs[i]->atttypid,
-								  &tcategory, &outfuncoid);
+			jsonb_categorize_type(att->atttypid, &tcategory, &outfuncoid);
 
 		datum_to_jsonb(val, isnull, result, tcategory, outfuncoid, false);
 	}
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index 4779e74895ce3b02b074d4349bf82a1048aa3794..d92ffa83d94ee66edf7e30a842c0d6f03520ccfd 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -3087,7 +3087,7 @@ populate_record(TupleDesc tupdesc,
 
 	for (i = 0; i < ncolumns; ++i)
 	{
-		Form_pg_attribute att = tupdesc->attrs[i];
+		Form_pg_attribute att = TupleDescAttr(tupdesc, i);
 		char	   *colname = NameStr(att->attname);
 		JsValue		field = {0};
 		bool		found;
diff --git a/src/backend/utils/adt/orderedsetaggs.c b/src/backend/utils/adt/orderedsetaggs.c
index 8502fcfc82e6dbb4c127dd926091b7e93ce4e912..25905a3287e602668366113a9dc418334060e74d 100644
--- a/src/backend/utils/adt/orderedsetaggs.c
+++ b/src/backend/utils/adt/orderedsetaggs.c
@@ -1125,13 +1125,15 @@ hypothetical_check_argtypes(FunctionCallInfo fcinfo, int nargs,
 	/* check that we have an int4 flag column */
 	if (!tupdesc ||
 		(nargs + 1) != tupdesc->natts ||
-		tupdesc->attrs[nargs]->atttypid != INT4OID)
+		TupleDescAttr(tupdesc, nargs)->atttypid != INT4OID)
 		elog(ERROR, "type mismatch in hypothetical-set function");
 
 	/* check that direct args match in type with aggregated args */
 	for (i = 0; i < nargs; i++)
 	{
-		if (get_fn_expr_argtype(fcinfo->flinfo, i + 1) != tupdesc->attrs[i]->atttypid)
+		Form_pg_attribute attr = TupleDescAttr(tupdesc, i);
+
+		if (get_fn_expr_argtype(fcinfo->flinfo, i + 1) != attr->atttypid)
 			elog(ERROR, "type mismatch in hypothetical-set function");
 	}
 }
diff --git a/src/backend/utils/adt/rowtypes.c b/src/backend/utils/adt/rowtypes.c
index 44acd13c6b19c17f083685666347c375b06455e8..98fe00ff394c329dadb71e80069626dda1d8d378 100644
--- a/src/backend/utils/adt/rowtypes.c
+++ b/src/backend/utils/adt/rowtypes.c
@@ -159,12 +159,13 @@ record_in(PG_FUNCTION_ARGS)
 
 	for (i = 0; i < ncolumns; i++)
 	{
+		Form_pg_attribute att = TupleDescAttr(tupdesc, i);
 		ColumnIOData *column_info = &my_extra->columns[i];
-		Oid			column_type = tupdesc->attrs[i]->atttypid;
+		Oid			column_type = att->atttypid;
 		char	   *column_data;
 
 		/* Ignore dropped columns in datatype, but fill with nulls */
-		if (tupdesc->attrs[i]->attisdropped)
+		if (att->attisdropped)
 		{
 			values[i] = (Datum) 0;
 			nulls[i] = true;
@@ -252,7 +253,7 @@ record_in(PG_FUNCTION_ARGS)
 		values[i] = InputFunctionCall(&column_info->proc,
 									  column_data,
 									  column_info->typioparam,
-									  tupdesc->attrs[i]->atttypmod);
+									  att->atttypmod);
 
 		/*
 		 * Prep for next column
@@ -367,15 +368,16 @@ record_out(PG_FUNCTION_ARGS)
 
 	for (i = 0; i < ncolumns; i++)
 	{
+		Form_pg_attribute att = TupleDescAttr(tupdesc, i);
 		ColumnIOData *column_info = &my_extra->columns[i];
-		Oid			column_type = tupdesc->attrs[i]->atttypid;
+		Oid			column_type = att->atttypid;
 		Datum		attr;
 		char	   *value;
 		char	   *tmp;
 		bool		nq;
 
 		/* Ignore dropped columns in datatype */
-		if (tupdesc->attrs[i]->attisdropped)
+		if (att->attisdropped)
 			continue;
 
 		if (needComma)
@@ -519,7 +521,7 @@ record_recv(PG_FUNCTION_ARGS)
 	validcols = 0;
 	for (i = 0; i < ncolumns; i++)
 	{
-		if (!tupdesc->attrs[i]->attisdropped)
+		if (!TupleDescAttr(tupdesc, i)->attisdropped)
 			validcols++;
 	}
 	if (usercols != validcols)
@@ -531,8 +533,9 @@ record_recv(PG_FUNCTION_ARGS)
 	/* Process each column */
 	for (i = 0; i < ncolumns; i++)
 	{
+		Form_pg_attribute att = TupleDescAttr(tupdesc, i);
 		ColumnIOData *column_info = &my_extra->columns[i];
-		Oid			column_type = tupdesc->attrs[i]->atttypid;
+		Oid			column_type = att->atttypid;
 		Oid			coltypoid;
 		int			itemlen;
 		StringInfoData item_buf;
@@ -540,7 +543,7 @@ record_recv(PG_FUNCTION_ARGS)
 		char		csave;
 
 		/* Ignore dropped columns in datatype, but fill with nulls */
-		if (tupdesc->attrs[i]->attisdropped)
+		if (att->attisdropped)
 		{
 			values[i] = (Datum) 0;
 			nulls[i] = true;
@@ -605,7 +608,7 @@ record_recv(PG_FUNCTION_ARGS)
 		values[i] = ReceiveFunctionCall(&column_info->proc,
 										bufptr,
 										column_info->typioparam,
-										tupdesc->attrs[i]->atttypmod);
+										att->atttypmod);
 
 		if (bufptr)
 		{
@@ -712,20 +715,21 @@ record_send(PG_FUNCTION_ARGS)
 	validcols = 0;
 	for (i = 0; i < ncolumns; i++)
 	{
-		if (!tupdesc->attrs[i]->attisdropped)
+		if (!TupleDescAttr(tupdesc, i)->attisdropped)
 			validcols++;
 	}
 	pq_sendint(&buf, validcols, 4);
 
 	for (i = 0; i < ncolumns; i++)
 	{
+		Form_pg_attribute att = TupleDescAttr(tupdesc, i);
 		ColumnIOData *column_info = &my_extra->columns[i];
-		Oid			column_type = tupdesc->attrs[i]->atttypid;
+		Oid			column_type = att->atttypid;
 		Datum		attr;
 		bytea	   *outputbytes;
 
 		/* Ignore dropped columns in datatype */
-		if (tupdesc->attrs[i]->attisdropped)
+		if (att->attisdropped)
 			continue;
 
 		pq_sendint(&buf, column_type, sizeof(Oid));
@@ -873,18 +877,20 @@ record_cmp(FunctionCallInfo fcinfo)
 	i1 = i2 = j = 0;
 	while (i1 < ncolumns1 || i2 < ncolumns2)
 	{
+		Form_pg_attribute att1;
+		Form_pg_attribute att2;
 		TypeCacheEntry *typentry;
 		Oid			collation;
 
 		/*
 		 * Skip dropped columns
 		 */
-		if (i1 < ncolumns1 && tupdesc1->attrs[i1]->attisdropped)
+		if (i1 < ncolumns1 && TupleDescAttr(tupdesc1, i1)->attisdropped)
 		{
 			i1++;
 			continue;
 		}
-		if (i2 < ncolumns2 && tupdesc2->attrs[i2]->attisdropped)
+		if (i2 < ncolumns2 && TupleDescAttr(tupdesc2, i2)->attisdropped)
 		{
 			i2++;
 			continue;
@@ -892,24 +898,26 @@ record_cmp(FunctionCallInfo fcinfo)
 		if (i1 >= ncolumns1 || i2 >= ncolumns2)
 			break;				/* we'll deal with mismatch below loop */
 
+		att1 = TupleDescAttr(tupdesc1, i1);
+		att2 = TupleDescAttr(tupdesc2, i2);
+
 		/*
 		 * Have two matching columns, they must be same type
 		 */
-		if (tupdesc1->attrs[i1]->atttypid !=
-			tupdesc2->attrs[i2]->atttypid)
+		if (att1->atttypid != att2->atttypid)
 			ereport(ERROR,
 					(errcode(ERRCODE_DATATYPE_MISMATCH),
 					 errmsg("cannot compare dissimilar column types %s and %s at record column %d",
-							format_type_be(tupdesc1->attrs[i1]->atttypid),
-							format_type_be(tupdesc2->attrs[i2]->atttypid),
+							format_type_be(att1->atttypid),
+							format_type_be(att2->atttypid),
 							j + 1)));
 
 		/*
 		 * If they're not same collation, we don't complain here, but the
 		 * comparison function might.
 		 */
-		collation = tupdesc1->attrs[i1]->attcollation;
-		if (collation != tupdesc2->attrs[i2]->attcollation)
+		collation = att1->attcollation;
+		if (collation != att2->attcollation)
 			collation = InvalidOid;
 
 		/*
@@ -917,9 +925,9 @@ record_cmp(FunctionCallInfo fcinfo)
 		 */
 		typentry = my_extra->columns[j].typentry;
 		if (typentry == NULL ||
-			typentry->type_id != tupdesc1->attrs[i1]->atttypid)
+			typentry->type_id != att1->atttypid)
 		{
-			typentry = lookup_type_cache(tupdesc1->attrs[i1]->atttypid,
+			typentry = lookup_type_cache(att1->atttypid,
 										 TYPECACHE_CMP_PROC_FINFO);
 			if (!OidIsValid(typentry->cmp_proc_finfo.fn_oid))
 				ereport(ERROR,
@@ -1111,6 +1119,8 @@ record_eq(PG_FUNCTION_ARGS)
 	i1 = i2 = j = 0;
 	while (i1 < ncolumns1 || i2 < ncolumns2)
 	{
+		Form_pg_attribute att1;
+		Form_pg_attribute att2;
 		TypeCacheEntry *typentry;
 		Oid			collation;
 		FunctionCallInfoData locfcinfo;
@@ -1119,12 +1129,12 @@ record_eq(PG_FUNCTION_ARGS)
 		/*
 		 * Skip dropped columns
 		 */
-		if (i1 < ncolumns1 && tupdesc1->attrs[i1]->attisdropped)
+		if (i1 < ncolumns1 && TupleDescAttr(tupdesc1, i1)->attisdropped)
 		{
 			i1++;
 			continue;
 		}
-		if (i2 < ncolumns2 && tupdesc2->attrs[i2]->attisdropped)
+		if (i2 < ncolumns2 && TupleDescAttr(tupdesc2, i2)->attisdropped)
 		{
 			i2++;
 			continue;
@@ -1132,24 +1142,26 @@ record_eq(PG_FUNCTION_ARGS)
 		if (i1 >= ncolumns1 || i2 >= ncolumns2)
 			break;				/* we'll deal with mismatch below loop */
 
+		att1 = TupleDescAttr(tupdesc1, i1);
+		att2 = TupleDescAttr(tupdesc2, i2);
+
 		/*
 		 * Have two matching columns, they must be same type
 		 */
-		if (tupdesc1->attrs[i1]->atttypid !=
-			tupdesc2->attrs[i2]->atttypid)
+		if (att1->atttypid != att2->atttypid)
 			ereport(ERROR,
 					(errcode(ERRCODE_DATATYPE_MISMATCH),
 					 errmsg("cannot compare dissimilar column types %s and %s at record column %d",
-							format_type_be(tupdesc1->attrs[i1]->atttypid),
-							format_type_be(tupdesc2->attrs[i2]->atttypid),
+							format_type_be(att1->atttypid),
+							format_type_be(att2->atttypid),
 							j + 1)));
 
 		/*
 		 * If they're not same collation, we don't complain here, but the
 		 * equality function might.
 		 */
-		collation = tupdesc1->attrs[i1]->attcollation;
-		if (collation != tupdesc2->attrs[i2]->attcollation)
+		collation = att1->attcollation;
+		if (collation != att2->attcollation)
 			collation = InvalidOid;
 
 		/*
@@ -1157,9 +1169,9 @@ record_eq(PG_FUNCTION_ARGS)
 		 */
 		typentry = my_extra->columns[j].typentry;
 		if (typentry == NULL ||
-			typentry->type_id != tupdesc1->attrs[i1]->atttypid)
+			typentry->type_id != att1->atttypid)
 		{
-			typentry = lookup_type_cache(tupdesc1->attrs[i1]->atttypid,
+			typentry = lookup_type_cache(att1->atttypid,
 										 TYPECACHE_EQ_OPR_FINFO);
 			if (!OidIsValid(typentry->eq_opr_finfo.fn_oid))
 				ereport(ERROR,
@@ -1370,15 +1382,18 @@ record_image_cmp(FunctionCallInfo fcinfo)
 	i1 = i2 = j = 0;
 	while (i1 < ncolumns1 || i2 < ncolumns2)
 	{
+		Form_pg_attribute att1;
+		Form_pg_attribute att2;
+
 		/*
 		 * Skip dropped columns
 		 */
-		if (i1 < ncolumns1 && tupdesc1->attrs[i1]->attisdropped)
+		if (i1 < ncolumns1 && TupleDescAttr(tupdesc1, i1)->attisdropped)
 		{
 			i1++;
 			continue;
 		}
-		if (i2 < ncolumns2 && tupdesc2->attrs[i2]->attisdropped)
+		if (i2 < ncolumns2 && TupleDescAttr(tupdesc2, i2)->attisdropped)
 		{
 			i2++;
 			continue;
@@ -1386,24 +1401,25 @@ record_image_cmp(FunctionCallInfo fcinfo)
 		if (i1 >= ncolumns1 || i2 >= ncolumns2)
 			break;				/* we'll deal with mismatch below loop */
 
+		att1 = TupleDescAttr(tupdesc1, i1);
+		att2 = TupleDescAttr(tupdesc2, i2);
+
 		/*
 		 * Have two matching columns, they must be same type
 		 */
-		if (tupdesc1->attrs[i1]->atttypid !=
-			tupdesc2->attrs[i2]->atttypid)
+		if (att1->atttypid != att2->atttypid)
 			ereport(ERROR,
 					(errcode(ERRCODE_DATATYPE_MISMATCH),
 					 errmsg("cannot compare dissimilar column types %s and %s at record column %d",
-							format_type_be(tupdesc1->attrs[i1]->atttypid),
-							format_type_be(tupdesc2->attrs[i2]->atttypid),
+							format_type_be(att1->atttypid),
+							format_type_be(att2->atttypid),
 							j + 1)));
 
 		/*
 		 * The same type should have the same length (or both should be
 		 * variable).
 		 */
-		Assert(tupdesc1->attrs[i1]->attlen ==
-			   tupdesc2->attrs[i2]->attlen);
+		Assert(att1->attlen == att2->attlen);
 
 		/*
 		 * We consider two NULLs equal; NULL > not-NULL.
@@ -1426,7 +1442,7 @@ record_image_cmp(FunctionCallInfo fcinfo)
 			}
 
 			/* Compare the pair of elements */
-			if (tupdesc1->attrs[i1]->attlen == -1)
+			if (att1->attlen == -1)
 			{
 				Size		len1,
 							len2;
@@ -1449,9 +1465,9 @@ record_image_cmp(FunctionCallInfo fcinfo)
 				if ((Pointer) arg2val != (Pointer) values2[i2])
 					pfree(arg2val);
 			}
-			else if (tupdesc1->attrs[i1]->attbyval)
+			else if (att1->attbyval)
 			{
-				switch (tupdesc1->attrs[i1]->attlen)
+				switch (att1->attlen)
 				{
 					case 1:
 						if (GET_1_BYTE(values1[i1]) !=
@@ -1495,7 +1511,7 @@ record_image_cmp(FunctionCallInfo fcinfo)
 			{
 				cmpresult = memcmp(DatumGetPointer(values1[i1]),
 								   DatumGetPointer(values2[i2]),
-								   tupdesc1->attrs[i1]->attlen);
+								   att1->attlen);
 			}
 
 			if (cmpresult < 0)
@@ -1647,15 +1663,18 @@ record_image_eq(PG_FUNCTION_ARGS)
 	i1 = i2 = j = 0;
 	while (i1 < ncolumns1 || i2 < ncolumns2)
 	{
+		Form_pg_attribute att1;
+		Form_pg_attribute att2;
+
 		/*
 		 * Skip dropped columns
 		 */
-		if (i1 < ncolumns1 && tupdesc1->attrs[i1]->attisdropped)
+		if (i1 < ncolumns1 && TupleDescAttr(tupdesc1, i1)->attisdropped)
 		{
 			i1++;
 			continue;
 		}
-		if (i2 < ncolumns2 && tupdesc2->attrs[i2]->attisdropped)
+		if (i2 < ncolumns2 && TupleDescAttr(tupdesc2, i2)->attisdropped)
 		{
 			i2++;
 			continue;
@@ -1663,16 +1682,18 @@ record_image_eq(PG_FUNCTION_ARGS)
 		if (i1 >= ncolumns1 || i2 >= ncolumns2)
 			break;				/* we'll deal with mismatch below loop */
 
+		att1 = TupleDescAttr(tupdesc1, i1);
+		att2 = TupleDescAttr(tupdesc2, i2);
+
 		/*
 		 * Have two matching columns, they must be same type
 		 */
-		if (tupdesc1->attrs[i1]->atttypid !=
-			tupdesc2->attrs[i2]->atttypid)
+		if (att1->atttypid != att2->atttypid)
 			ereport(ERROR,
 					(errcode(ERRCODE_DATATYPE_MISMATCH),
 					 errmsg("cannot compare dissimilar column types %s and %s at record column %d",
-							format_type_be(tupdesc1->attrs[i1]->atttypid),
-							format_type_be(tupdesc2->attrs[i2]->atttypid),
+							format_type_be(att1->atttypid),
+							format_type_be(att2->atttypid),
 							j + 1)));
 
 		/*
@@ -1687,7 +1708,7 @@ record_image_eq(PG_FUNCTION_ARGS)
 			}
 
 			/* Compare the pair of elements */
-			if (tupdesc1->attrs[i1]->attlen == -1)
+			if (att1->attlen == -1)
 			{
 				Size		len1,
 							len2;
@@ -1716,9 +1737,9 @@ record_image_eq(PG_FUNCTION_ARGS)
 						pfree(arg2val);
 				}
 			}
-			else if (tupdesc1->attrs[i1]->attbyval)
+			else if (att1->attbyval)
 			{
-				switch (tupdesc1->attrs[i1]->attlen)
+				switch (att1->attlen)
 				{
 					case 1:
 						result = (GET_1_BYTE(values1[i1]) ==
@@ -1746,7 +1767,7 @@ record_image_eq(PG_FUNCTION_ARGS)
 			{
 				result = (memcmp(DatumGetPointer(values1[i1]),
 								 DatumGetPointer(values2[i2]),
-								 tupdesc1->attrs[i1]->attlen) == 0);
+								 att1->attlen) == 0);
 			}
 			if (!result)
 				break;
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 7469ec773c6abc629923d273759056682985f289..43646d2c4f8558f75a72e2e3dd5c3c6e0bd64cd5 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -3698,10 +3698,12 @@ set_relation_column_names(deparse_namespace *dpns, RangeTblEntry *rte,
 
 		for (i = 0; i < ncolumns; i++)
 		{
-			if (tupdesc->attrs[i]->attisdropped)
+			Form_pg_attribute attr = TupleDescAttr(tupdesc, i);
+
+			if (attr->attisdropped)
 				real_colnames[i] = NULL;
 			else
-				real_colnames[i] = pstrdup(NameStr(tupdesc->attrs[i]->attname));
+				real_colnames[i] = pstrdup(NameStr(attr->attname));
 		}
 		relation_close(rel, AccessShareLock);
 	}
@@ -5391,7 +5393,7 @@ get_target_list(List *targetList, deparse_context *context,
 		 * Otherwise, just use what we can find in the TLE.
 		 */
 		if (resultDesc && colno <= resultDesc->natts)
-			colname = NameStr(resultDesc->attrs[colno - 1]->attname);
+			colname = NameStr(TupleDescAttr(resultDesc, colno - 1)->attname);
 		else
 			colname = tle->resname;
 
@@ -6741,7 +6743,7 @@ get_name_for_var_field(Var *var, int fieldno,
 		Assert(tupleDesc);
 		/* Got the tupdesc, so we can extract the field name */
 		Assert(fieldno >= 1 && fieldno <= tupleDesc->natts);
-		return NameStr(tupleDesc->attrs[fieldno - 1]->attname);
+		return NameStr(TupleDescAttr(tupleDesc, fieldno - 1)->attname);
 	}
 
 	/* Find appropriate nesting depth */
@@ -7051,7 +7053,7 @@ get_name_for_var_field(Var *var, int fieldno,
 	Assert(tupleDesc);
 	/* Got the tupdesc, so we can extract the field name */
 	Assert(fieldno >= 1 && fieldno <= tupleDesc->natts);
-	return NameStr(tupleDesc->attrs[fieldno - 1]->attname);
+	return NameStr(TupleDescAttr(tupleDesc, fieldno - 1)->attname);
 }
 
 /*
@@ -8180,7 +8182,7 @@ get_rule_expr(Node *node, deparse_context *context,
 					Node	   *e = (Node *) lfirst(arg);
 
 					if (tupdesc == NULL ||
-						!tupdesc->attrs[i]->attisdropped)
+						!TupleDescAttr(tupdesc, i)->attisdropped)
 					{
 						appendStringInfoString(buf, sep);
 						/* Whole-row Vars need special treatment here */
@@ -8193,7 +8195,7 @@ get_rule_expr(Node *node, deparse_context *context,
 				{
 					while (i < tupdesc->natts)
 					{
-						if (!tupdesc->attrs[i]->attisdropped)
+						if (!TupleDescAttr(tupdesc, i)->attisdropped)
 						{
 							appendStringInfoString(buf, sep);
 							appendStringInfoString(buf, "NULL");
diff --git a/src/backend/utils/adt/tid.c b/src/backend/utils/adt/tid.c
index 8453b65e78aaed5002e261ed1f66e5adf046d53e..083f7d60a7bcf9b075ea84543e15142d831d1f1b 100644
--- a/src/backend/utils/adt/tid.c
+++ b/src/backend/utils/adt/tid.c
@@ -273,9 +273,11 @@ currtid_for_view(Relation viewrel, ItemPointer tid)
 
 	for (i = 0; i < natts; i++)
 	{
-		if (strcmp(NameStr(att->attrs[i]->attname), "ctid") == 0)
+		Form_pg_attribute attr = TupleDescAttr(att, i);
+
+		if (strcmp(NameStr(attr->attname), "ctid") == 0)
 		{
-			if (att->attrs[i]->atttypid != TIDOID)
+			if (attr->atttypid != TIDOID)
 				elog(ERROR, "ctid isn't of type TID");
 			tididx = i;
 			break;
diff --git a/src/backend/utils/adt/xml.c b/src/backend/utils/adt/xml.c
index c47624eff686e198392b04d43b93396cfda47382..24229c2dff7ba3632ac796709f5bc00d30c1c8fc 100644
--- a/src/backend/utils/adt/xml.c
+++ b/src/backend/utils/adt/xml.c
@@ -3099,13 +3099,15 @@ map_sql_table_to_xmlschema(TupleDesc tupdesc, Oid relid, bool nulls,
 
 	for (i = 0; i < tupdesc->natts; i++)
 	{
-		if (tupdesc->attrs[i]->attisdropped)
+		Form_pg_attribute att = TupleDescAttr(tupdesc, i);
+
+		if (att->attisdropped)
 			continue;
 		appendStringInfo(&result,
 						 "    <xsd:element name=\"%s\" type=\"%s\"%s></xsd:element>\n",
-						 map_sql_identifier_to_xml_name(NameStr(tupdesc->attrs[i]->attname),
+						 map_sql_identifier_to_xml_name(NameStr(att->attname),
 														true, false),
-						 map_sql_type_to_xml_name(tupdesc->attrs[i]->atttypid, -1),
+						 map_sql_type_to_xml_name(att->atttypid, -1),
 						 nulls ? " nillable=\"true\"" : " minOccurs=\"0\"");
 	}
 
@@ -3392,10 +3394,11 @@ map_sql_typecoll_to_xmlschema_types(List *tupdesc_list)
 
 		for (i = 0; i < tupdesc->natts; i++)
 		{
-			if (tupdesc->attrs[i]->attisdropped)
+			Form_pg_attribute att = TupleDescAttr(tupdesc, i);
+
+			if (att->attisdropped)
 				continue;
-			uniquetypes = list_append_unique_oid(uniquetypes,
-												 tupdesc->attrs[i]->atttypid);
+			uniquetypes = list_append_unique_oid(uniquetypes, att->atttypid);
 		}
 	}
 
diff --git a/src/backend/utils/cache/catcache.c b/src/backend/utils/cache/catcache.c
index f894053d80676d9eabb87020fe63aefde63b502e..e09280102575176271358bf0e63efdfd1b60c2ac 100644
--- a/src/backend/utils/cache/catcache.c
+++ b/src/backend/utils/cache/catcache.c
@@ -797,7 +797,7 @@ do { \
 		if (cache->cc_key[i] > 0) { \
 			elog(DEBUG2, "CatalogCacheInitializeCache: load %d/%d w/%d, %u", \
 				i+1, cache->cc_nkeys, cache->cc_key[i], \
-				 tupdesc->attrs[cache->cc_key[i] - 1]->atttypid); \
+				 TupleDescAttr(tupdesc, cache->cc_key[i] - 1)->atttypid); \
 		} else { \
 			elog(DEBUG2, "CatalogCacheInitializeCache: load %d/%d w/%d", \
 				i+1, cache->cc_nkeys, cache->cc_key[i]); \
@@ -862,7 +862,8 @@ CatalogCacheInitializeCache(CatCache *cache)
 
 		if (cache->cc_key[i] > 0)
 		{
-			Form_pg_attribute attr = tupdesc->attrs[cache->cc_key[i] - 1];
+			Form_pg_attribute attr = TupleDescAttr(tupdesc,
+												   cache->cc_key[i] - 1);
 
 			keytype = attr->atttypid;
 			/* cache key columns should always be NOT NULL */
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 2150fe9a3964079d7870af1f841662e6484e03ca..b8e37809b0507bb5978bce2d01f446420bf081d5 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -546,7 +546,7 @@ RelationBuildTupleDesc(Relation relation)
 			elog(ERROR, "invalid attribute number %d for %s",
 				 attp->attnum, RelationGetRelationName(relation));
 
-		memcpy(relation->rd_att->attrs[attp->attnum - 1],
+		memcpy(TupleDescAttr(relation->rd_att, attp->attnum - 1),
 			   attp,
 			   ATTRIBUTE_FIXED_PART_SIZE);
 
@@ -590,7 +590,7 @@ RelationBuildTupleDesc(Relation relation)
 		int			i;
 
 		for (i = 0; i < relation->rd_rel->relnatts; i++)
-			Assert(relation->rd_att->attrs[i]->attcacheoff == -1);
+			Assert(TupleDescAttr(relation->rd_att, i)->attcacheoff == -1);
 	}
 #endif
 
@@ -600,7 +600,7 @@ RelationBuildTupleDesc(Relation relation)
 	 * for attnum=1 that used to exist in fastgetattr() and index_getattr().
 	 */
 	if (relation->rd_rel->relnatts > 0)
-		relation->rd_att->attrs[0]->attcacheoff = 0;
+		TupleDescAttr(relation->rd_att, 0)->attcacheoff = 0;
 
 	/*
 	 * Set up constraint/default info
@@ -958,9 +958,11 @@ RelationBuildPartitionKey(Relation relation)
 		/* Collect type information */
 		if (attno != 0)
 		{
-			key->parttypid[i] = relation->rd_att->attrs[attno - 1]->atttypid;
-			key->parttypmod[i] = relation->rd_att->attrs[attno - 1]->atttypmod;
-			key->parttypcoll[i] = relation->rd_att->attrs[attno - 1]->attcollation;
+			Form_pg_attribute att = TupleDescAttr(relation->rd_att, attno - 1);
+
+			key->parttypid[i] = att->atttypid;
+			key->parttypmod[i] = att->atttypmod;
+			key->parttypcoll[i] = att->attcollation;
 		}
 		else
 		{
@@ -1977,16 +1979,16 @@ formrdesc(const char *relationName, Oid relationReltype,
 	has_not_null = false;
 	for (i = 0; i < natts; i++)
 	{
-		memcpy(relation->rd_att->attrs[i],
+		memcpy(TupleDescAttr(relation->rd_att, i),
 			   &attrs[i],
 			   ATTRIBUTE_FIXED_PART_SIZE);
 		has_not_null |= attrs[i].attnotnull;
 		/* make sure attcacheoff is valid */
-		relation->rd_att->attrs[i]->attcacheoff = -1;
+		TupleDescAttr(relation->rd_att, i)->attcacheoff = -1;
 	}
 
 	/* initialize first attribute's attcacheoff, cf RelationBuildTupleDesc */
-	relation->rd_att->attrs[0]->attcacheoff = 0;
+	TupleDescAttr(relation->rd_att, 0)->attcacheoff = 0;
 
 	/* mark not-null status */
 	if (has_not_null)
@@ -2000,7 +2002,7 @@ formrdesc(const char *relationName, Oid relationReltype,
 	/*
 	 * initialize relation id from info in att array (my, this is ugly)
 	 */
-	RelationGetRelid(relation) = relation->rd_att->attrs[0]->attrelid;
+	RelationGetRelid(relation) = TupleDescAttr(relation->rd_att, 0)->attrelid;
 
 	/*
 	 * All relations made with formrdesc are mapped.  This is necessarily so
@@ -3274,9 +3276,12 @@ RelationBuildLocalRelation(const char *relname,
 	has_not_null = false;
 	for (i = 0; i < natts; i++)
 	{
-		rel->rd_att->attrs[i]->attidentity = tupDesc->attrs[i]->attidentity;
-		rel->rd_att->attrs[i]->attnotnull = tupDesc->attrs[i]->attnotnull;
-		has_not_null |= tupDesc->attrs[i]->attnotnull;
+		Form_pg_attribute satt = TupleDescAttr(tupDesc, i);
+		Form_pg_attribute datt = TupleDescAttr(rel->rd_att, i);
+
+		datt->attidentity = satt->attidentity;
+		datt->attnotnull = satt->attnotnull;
+		has_not_null |= satt->attnotnull;
 	}
 
 	if (has_not_null)
@@ -3346,7 +3351,7 @@ RelationBuildLocalRelation(const char *relname,
 	RelationGetRelid(rel) = relid;
 
 	for (i = 0; i < natts; i++)
-		rel->rd_att->attrs[i]->attrelid = relid;
+		TupleDescAttr(rel->rd_att, i)->attrelid = relid;
 
 	rel->rd_rel->reltablespace = reltablespace;
 
@@ -3971,13 +3976,13 @@ BuildHardcodedDescriptor(int natts, const FormData_pg_attribute *attrs,
 
 	for (i = 0; i < natts; i++)
 	{
-		memcpy(result->attrs[i], &attrs[i], ATTRIBUTE_FIXED_PART_SIZE);
+		memcpy(TupleDescAttr(result, i), &attrs[i], ATTRIBUTE_FIXED_PART_SIZE);
 		/* make sure attcacheoff is valid */
-		result->attrs[i]->attcacheoff = -1;
+		TupleDescAttr(result, i)->attcacheoff = -1;
 	}
 
 	/* initialize first attribute's attcacheoff, cf RelationBuildTupleDesc */
-	result->attrs[0]->attcacheoff = 0;
+	TupleDescAttr(result, 0)->attcacheoff = 0;
 
 	/* Note: we don't bother to set up a TupleConstr entry */
 
@@ -4044,6 +4049,7 @@ AttrDefaultFetch(Relation relation)
 	while (HeapTupleIsValid(htup = systable_getnext(adscan)))
 	{
 		Form_pg_attrdef adform = (Form_pg_attrdef) GETSTRUCT(htup);
+		Form_pg_attribute attr = TupleDescAttr(relation->rd_att, adform->adnum - 1);
 
 		for (i = 0; i < ndef; i++)
 		{
@@ -4051,7 +4057,7 @@ AttrDefaultFetch(Relation relation)
 				continue;
 			if (attrdef[i].adbin != NULL)
 				elog(WARNING, "multiple attrdef records found for attr %s of rel %s",
-					 NameStr(relation->rd_att->attrs[adform->adnum - 1]->attname),
+					 NameStr(attr->attname),
 					 RelationGetRelationName(relation));
 			else
 				found++;
@@ -4061,7 +4067,7 @@ AttrDefaultFetch(Relation relation)
 							  adrel->rd_att, &isnull);
 			if (isnull)
 				elog(WARNING, "null adbin for attr %s of rel %s",
-					 NameStr(relation->rd_att->attrs[adform->adnum - 1]->attname),
+					 NameStr(attr->attname),
 					 RelationGetRelationName(relation));
 			else
 			{
@@ -5270,7 +5276,7 @@ errtablecol(Relation rel, int attnum)
 
 	/* Use reldesc if it's a user attribute, else consult the catalogs */
 	if (attnum > 0 && attnum <= reldesc->natts)
-		colname = NameStr(reldesc->attrs[attnum - 1]->attname);
+		colname = NameStr(TupleDescAttr(reldesc, attnum - 1)->attname);
 	else
 		colname = get_relid_attribute_name(RelationGetRelid(rel), attnum);
 
@@ -5460,14 +5466,16 @@ load_relcache_init_file(bool shared)
 		has_not_null = false;
 		for (i = 0; i < relform->relnatts; i++)
 		{
+			Form_pg_attribute attr = TupleDescAttr(rel->rd_att, i);
+
 			if (fread(&len, 1, sizeof(len), fp) != sizeof(len))
 				goto read_failed;
 			if (len != ATTRIBUTE_FIXED_PART_SIZE)
 				goto read_failed;
-			if (fread(rel->rd_att->attrs[i], 1, len, fp) != len)
+			if (fread(attr, 1, len, fp) != len)
 				goto read_failed;
 
-			has_not_null |= rel->rd_att->attrs[i]->attnotnull;
+			has_not_null |= attr->attnotnull;
 		}
 
 		/* next read the access method specific field */
@@ -5848,7 +5856,8 @@ write_relcache_init_file(bool shared)
 		/* next, do all the attribute tuple form data entries */
 		for (i = 0; i < relform->relnatts; i++)
 		{
-			write_item(rel->rd_att->attrs[i], ATTRIBUTE_FIXED_PART_SIZE, fp);
+			write_item(TupleDescAttr(rel->rd_att, i),
+					   ATTRIBUTE_FIXED_PART_SIZE, fp);
 		}
 
 		/* next, do the access method specific field */
diff --git a/src/backend/utils/cache/typcache.c b/src/backend/utils/cache/typcache.c
index 7ec31eb3e32683f5e3fb87e3b41527257cae93df..20567a394b2109bd93f69b4305a82c7ac6b1b48b 100644
--- a/src/backend/utils/cache/typcache.c
+++ b/src/backend/utils/cache/typcache.c
@@ -1176,11 +1176,12 @@ cache_record_field_properties(TypeCacheEntry *typentry)
 		for (i = 0; i < tupdesc->natts; i++)
 		{
 			TypeCacheEntry *fieldentry;
+			Form_pg_attribute attr = TupleDescAttr(tupdesc, i);
 
-			if (tupdesc->attrs[i]->attisdropped)
+			if (attr->attisdropped)
 				continue;
 
-			fieldentry = lookup_type_cache(tupdesc->attrs[i]->atttypid,
+			fieldentry = lookup_type_cache(attr->atttypid,
 										   TYPECACHE_EQ_OPR |
 										   TYPECACHE_CMP_PROC);
 			if (!OidIsValid(fieldentry->eq_opr))
@@ -1340,7 +1341,7 @@ assign_record_type_typmod(TupleDesc tupDesc)
 	{
 		if (i >= REC_HASH_KEYS)
 			break;
-		hashkey[i] = tupDesc->attrs[i]->atttypid;
+		hashkey[i] = TupleDescAttr(tupDesc, i)->atttypid;
 	}
 	recentry = (RecordCacheEntry *) hash_search(RecordCacheHash,
 												(void *) hashkey,
diff --git a/src/backend/utils/fmgr/funcapi.c b/src/backend/utils/fmgr/funcapi.c
index be47d411c5bdf51eb52ac96fa416672f5dc286ba..9c3f4510ce4aed20d77a8b3a24d5a99ad943ed05 100644
--- a/src/backend/utils/fmgr/funcapi.c
+++ b/src/backend/utils/fmgr/funcapi.c
@@ -419,7 +419,7 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 	/* See if there are any polymorphic outputs; quick out if not */
 	for (i = 0; i < natts; i++)
 	{
-		switch (tupdesc->attrs[i]->atttypid)
+		switch (TupleDescAttr(tupdesc, i)->atttypid)
 		{
 			case ANYELEMENTOID:
 				have_anyelement_result = true;
@@ -548,13 +548,15 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 	/* And finally replace the tuple column types as needed */
 	for (i = 0; i < natts; i++)
 	{
-		switch (tupdesc->attrs[i]->atttypid)
+		Form_pg_attribute att = TupleDescAttr(tupdesc, i);
+
+		switch (att->atttypid)
 		{
 			case ANYELEMENTOID:
 			case ANYNONARRAYOID:
 			case ANYENUMOID:
 				TupleDescInitEntry(tupdesc, i + 1,
-								   NameStr(tupdesc->attrs[i]->attname),
+								   NameStr(att->attname),
 								   anyelement_type,
 								   -1,
 								   0);
@@ -562,7 +564,7 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 				break;
 			case ANYARRAYOID:
 				TupleDescInitEntry(tupdesc, i + 1,
-								   NameStr(tupdesc->attrs[i]->attname),
+								   NameStr(att->attname),
 								   anyarray_type,
 								   -1,
 								   0);
@@ -570,7 +572,7 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 				break;
 			case ANYRANGEOID:
 				TupleDescInitEntry(tupdesc, i + 1,
-								   NameStr(tupdesc->attrs[i]->attname),
+								   NameStr(att->attname),
 								   anyrange_type,
 								   -1,
 								   0);
@@ -1344,9 +1346,10 @@ TypeGetTupleDesc(Oid typeoid, List *colaliases)
 			for (varattno = 0; varattno < natts; varattno++)
 			{
 				char	   *label = strVal(list_nth(colaliases, varattno));
+				Form_pg_attribute attr = TupleDescAttr(tupdesc, varattno);
 
 				if (label != NULL)
-					namestrcpy(&(tupdesc->attrs[varattno]->attname), label);
+					namestrcpy(&(attr->attname), label);
 			}
 
 			/* The tuple type is now an anonymous record type */
diff --git a/src/backend/utils/misc/pg_config.c b/src/backend/utils/misc/pg_config.c
index 468c7cc9e12936808674254625999f5ef32742f9..a84878994c72e845b18b38c5a95bbcecd3e5262e 100644
--- a/src/backend/utils/misc/pg_config.c
+++ b/src/backend/utils/misc/pg_config.c
@@ -54,8 +54,8 @@ pg_config(PG_FUNCTION_ARGS)
 	 * Check to make sure we have a reasonable tuple descriptor
 	 */
 	if (tupdesc->natts != 2 ||
-		tupdesc->attrs[0]->atttypid != TEXTOID ||
-		tupdesc->attrs[1]->atttypid != TEXTOID)
+		TupleDescAttr(tupdesc, 0)->atttypid != TEXTOID ||
+		TupleDescAttr(tupdesc, 1)->atttypid != TEXTOID)
 		ereport(ERROR,
 				(errcode(ERRCODE_SYNTAX_ERROR),
 				 errmsg("query-specified return tuple and "
diff --git a/src/include/access/htup_details.h b/src/include/access/htup_details.h
index 3e1676c7e6146c1ab8170a2ca7857977ae74c995..fa04a63b760bc77da01f87dff984c07d32987988 100644
--- a/src/include/access/htup_details.h
+++ b/src/include/access/htup_details.h
@@ -722,11 +722,11 @@ struct MinimalTupleData
 	(*(isnull) = false),											\
 	HeapTupleNoNulls(tup) ?											\
 	(																\
-		(tupleDesc)->attrs[(attnum)-1]->attcacheoff >= 0 ?			\
+		TupleDescAttr((tupleDesc), (attnum)-1)->attcacheoff >= 0 ?	\
 		(															\
-			fetchatt((tupleDesc)->attrs[(attnum)-1],				\
+			fetchatt(TupleDescAttr((tupleDesc), (attnum)-1),		\
 				(char *) (tup)->t_data + (tup)->t_data->t_hoff +	\
-					(tupleDesc)->attrs[(attnum)-1]->attcacheoff)	\
+				TupleDescAttr((tupleDesc), (attnum)-1)->attcacheoff)\
 		)															\
 		:															\
 			nocachegetattr((tup), (attnum), (tupleDesc))			\
diff --git a/src/include/access/itup.h b/src/include/access/itup.h
index a94e7948b49671293fba0af19c644e564d73da48..c178ae91a9c70bb65217f30a048a7be49f081ec9 100644
--- a/src/include/access/itup.h
+++ b/src/include/access/itup.h
@@ -103,11 +103,11 @@ typedef IndexAttributeBitMapData * IndexAttributeBitMap;
 	*(isnull) = false, \
 	!IndexTupleHasNulls(tup) ? \
 	( \
-		(tupleDesc)->attrs[(attnum)-1]->attcacheoff >= 0 ? \
+		TupleDescAttr((tupleDesc), (attnum)-1)->attcacheoff >= 0 ? \
 		( \
-			fetchatt((tupleDesc)->attrs[(attnum)-1], \
+			fetchatt(TupleDescAttr((tupleDesc), (attnum)-1), \
 			(char *) (tup) + IndexInfoFindDataOffset((tup)->t_info) \
-			+ (tupleDesc)->attrs[(attnum)-1]->attcacheoff) \
+			+ TupleDescAttr((tupleDesc), (attnum)-1)->attcacheoff) \
 		) \
 		: \
 			nocache_index_getattr((tup), (attnum), (tupleDesc)) \
diff --git a/src/include/access/tupdesc.h b/src/include/access/tupdesc.h
index e7065d70baadd8db7b5ba596a9f421464fc5c271..31b77a08fae87ba5376e2647fd29c53bfa3ed789 100644
--- a/src/include/access/tupdesc.h
+++ b/src/include/access/tupdesc.h
@@ -80,6 +80,8 @@ typedef struct tupleDesc
 	int			tdrefcount;		/* reference count, or -1 if not counting */
 }		   *TupleDesc;
 
+/* Accessor for the i'th attribute of tupdesc. */
+#define TupleDescAttr(tupdesc, i) ((tupdesc)->attrs[(i)])
 
 extern TupleDesc CreateTemplateTupleDesc(int natts, bool hasoid);
 
diff --git a/src/pl/plperl/plperl.c b/src/pl/plperl/plperl.c
index afebec910d6cb71ed1629457e63384901548faac..5a575bdbe4ec59262ab8e874f75c4bb1b1d98ca5 100644
--- a/src/pl/plperl/plperl.c
+++ b/src/pl/plperl/plperl.c
@@ -1095,6 +1095,7 @@ plperl_build_tuple_result(HV *perlhash, TupleDesc td)
 		SV		   *val = HeVAL(he);
 		char	   *key = hek2cstr(he);
 		int			attn = SPI_fnumber(td, key);
+		Form_pg_attribute attr = TupleDescAttr(td, attn - 1);
 
 		if (attn == SPI_ERROR_NOATTRIBUTE)
 			ereport(ERROR,
@@ -1108,8 +1109,8 @@ plperl_build_tuple_result(HV *perlhash, TupleDesc td)
 							key)));
 
 		values[attn - 1] = plperl_sv_to_datum(val,
-											  td->attrs[attn - 1]->atttypid,
-											  td->attrs[attn - 1]->atttypmod,
+											  attr->atttypid,
+											  attr->atttypmod,
 											  NULL,
 											  NULL,
 											  InvalidOid,
@@ -1757,6 +1758,7 @@ plperl_modify_tuple(HV *hvTD, TriggerData *tdata, HeapTuple otup)
 		char	   *key = hek2cstr(he);
 		SV		   *val = HeVAL(he);
 		int			attn = SPI_fnumber(tupdesc, key);
+		Form_pg_attribute attr = TupleDescAttr(tupdesc, attn - 1);
 
 		if (attn == SPI_ERROR_NOATTRIBUTE)
 			ereport(ERROR,
@@ -1770,8 +1772,8 @@ plperl_modify_tuple(HV *hvTD, TriggerData *tdata, HeapTuple otup)
 							key)));
 
 		modvalues[attn - 1] = plperl_sv_to_datum(val,
-												 tupdesc->attrs[attn - 1]->atttypid,
-												 tupdesc->attrs[attn - 1]->atttypmod,
+												 attr->atttypid,
+												 attr->atttypmod,
 												 NULL,
 												 NULL,
 												 InvalidOid,
@@ -3014,11 +3016,12 @@ plperl_hash_from_tuple(HeapTuple tuple, TupleDesc tupdesc)
 					typisvarlena;
 		char	   *attname;
 		Oid			typoutput;
+		Form_pg_attribute att = TupleDescAttr(tupdesc, i);
 
-		if (tupdesc->attrs[i]->attisdropped)
+		if (att->attisdropped)
 			continue;
 
-		attname = NameStr(tupdesc->attrs[i]->attname);
+		attname = NameStr(att->attname);
 		attr = heap_getattr(tuple, i + 1, tupdesc, &isnull);
 
 		if (isnull)
@@ -3032,7 +3035,7 @@ plperl_hash_from_tuple(HeapTuple tuple, TupleDesc tupdesc)
 			continue;
 		}
 
-		if (type_is_rowtype(tupdesc->attrs[i]->atttypid))
+		if (type_is_rowtype(att->atttypid))
 		{
 			SV		   *sv = plperl_hash_from_datum(attr);
 
@@ -3043,17 +3046,16 @@ plperl_hash_from_tuple(HeapTuple tuple, TupleDesc tupdesc)
 			SV		   *sv;
 			Oid			funcid;
 
-			if (OidIsValid(get_base_element_type(tupdesc->attrs[i]->atttypid)))
-				sv = plperl_ref_from_pg_array(attr, tupdesc->attrs[i]->atttypid);
-			else if ((funcid = get_transform_fromsql(tupdesc->attrs[i]->atttypid, current_call_data->prodesc->lang_oid, current_call_data->prodesc->trftypes)))
+			if (OidIsValid(get_base_element_type(att->atttypid)))
+				sv = plperl_ref_from_pg_array(attr, att->atttypid);
+			else if ((funcid = get_transform_fromsql(att->atttypid, current_call_data->prodesc->lang_oid, current_call_data->prodesc->trftypes)))
 				sv = (SV *) DatumGetPointer(OidFunctionCall1(funcid, attr));
 			else
 			{
 				char	   *outputstr;
 
 				/* XXX should have a way to cache these lookups */
-				getTypeOutputInfo(tupdesc->attrs[i]->atttypid,
-								  &typoutput, &typisvarlena);
+				getTypeOutputInfo(att->atttypid, &typoutput, &typisvarlena);
 
 				outputstr = OidOutputFunctionCall(typoutput, attr);
 				sv = cstr2sv(outputstr);
diff --git a/src/pl/plpgsql/src/pl_comp.c b/src/pl/plpgsql/src/pl_comp.c
index 662b3c97d7f164349276cdf4c6cd1d4d7b2202f0..e9d7ef55e97618e06ca0b6eb5ce596bf80f27684 100644
--- a/src/pl/plpgsql/src/pl_comp.c
+++ b/src/pl/plpgsql/src/pl_comp.c
@@ -2012,7 +2012,7 @@ build_row_from_class(Oid classOid)
 		/*
 		 * Get the attribute and check for dropped column
 		 */
-		attrStruct = row->rowtupdesc->attrs[i];
+		attrStruct = TupleDescAttr(row->rowtupdesc, i);
 
 		if (!attrStruct->attisdropped)
 		{
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index 616f5e30f8112d7d10624e34aae26be1a066227a..97166972594997492bc5c2971450743ba3f67f6d 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -2841,6 +2841,7 @@ exec_stmt_return_next(PLpgSQL_execstate *estate,
 					PLpgSQL_var *var = (PLpgSQL_var *) retvar;
 					Datum		retval = var->value;
 					bool		isNull = var->isnull;
+					Form_pg_attribute attr = TupleDescAttr(tupdesc, 0);
 
 					if (natts != 1)
 						ereport(ERROR,
@@ -2858,8 +2859,8 @@ exec_stmt_return_next(PLpgSQL_execstate *estate,
 											 &isNull,
 											 var->datatype->typoid,
 											 var->datatype->atttypmod,
-											 tupdesc->attrs[0]->atttypid,
-											 tupdesc->attrs[0]->atttypmod);
+											 attr->atttypid,
+											 attr->atttypmod);
 
 					tuplestore_putvalues(estate->tuple_store, tupdesc,
 										 &retval, &isNull);
@@ -2968,6 +2969,8 @@ exec_stmt_return_next(PLpgSQL_execstate *estate,
 		}
 		else
 		{
+			Form_pg_attribute attr = TupleDescAttr(tupdesc, 0);
+
 			/* Simple scalar result */
 			if (natts != 1)
 				ereport(ERROR,
@@ -2980,8 +2983,8 @@ exec_stmt_return_next(PLpgSQL_execstate *estate,
 									 &isNull,
 									 rettype,
 									 rettypmod,
-									 tupdesc->attrs[0]->atttypid,
-									 tupdesc->attrs[0]->atttypmod);
+									 attr->atttypid,
+									 attr->atttypmod);
 
 			tuplestore_putvalues(estate->tuple_store, tupdesc,
 								 &retval, &isNull);
@@ -4588,8 +4591,8 @@ exec_assign_value(PLpgSQL_execstate *estate,
 				 * Now insert the new value, being careful to cast it to the
 				 * right type.
 				 */
-				atttype = rec->tupdesc->attrs[fno - 1]->atttypid;
-				atttypmod = rec->tupdesc->attrs[fno - 1]->atttypmod;
+				atttype = TupleDescAttr(rec->tupdesc, fno - 1)->atttypid;
+				atttypmod = TupleDescAttr(rec->tupdesc, fno - 1)->atttypmod;
 				values[0] = exec_cast_value(estate,
 											value,
 											&isNull,
@@ -4913,7 +4916,11 @@ exec_eval_datum(PLpgSQL_execstate *estate,
 									rec->refname, recfield->fieldname)));
 				*typeid = SPI_gettypeid(rec->tupdesc, fno);
 				if (fno > 0)
-					*typetypmod = rec->tupdesc->attrs[fno - 1]->atttypmod;
+				{
+					Form_pg_attribute attr = TupleDescAttr(rec->tupdesc, fno - 1);
+
+					*typetypmod = attr->atttypmod;
+				}
 				else
 					*typetypmod = -1;
 				*value = SPI_getbinval(rec->tup, rec->tupdesc, fno, isnull);
@@ -5089,11 +5096,19 @@ plpgsql_exec_get_datum_type_info(PLpgSQL_execstate *estate,
 									rec->refname, recfield->fieldname)));
 				*typeid = SPI_gettypeid(rec->tupdesc, fno);
 				if (fno > 0)
-					*typmod = rec->tupdesc->attrs[fno - 1]->atttypmod;
+				{
+					Form_pg_attribute attr = TupleDescAttr(rec->tupdesc, fno - 1);
+
+					*typmod = attr->atttypmod;
+				}
 				else
 					*typmod = -1;
 				if (fno > 0)
-					*collation = rec->tupdesc->attrs[fno - 1]->attcollation;
+				{
+					Form_pg_attribute attr = TupleDescAttr(rec->tupdesc, fno - 1);
+
+					*collation = attr->attcollation;
+				}
 				else			/* no system column types have collation */
 					*collation = InvalidOid;
 				break;
@@ -5172,6 +5187,7 @@ exec_eval_expr(PLpgSQL_execstate *estate,
 {
 	Datum		result = 0;
 	int			rc;
+	Form_pg_attribute attr;
 
 	/*
 	 * If first time through, create a plan for this expression.
@@ -5211,8 +5227,9 @@ exec_eval_expr(PLpgSQL_execstate *estate,
 	/*
 	 * ... and get the column's datatype.
 	 */
-	*rettype = estate->eval_tuptable->tupdesc->attrs[0]->atttypid;
-	*rettypmod = estate->eval_tuptable->tupdesc->attrs[0]->atttypmod;
+	attr = TupleDescAttr(estate->eval_tuptable->tupdesc, 0);
+	*rettype = attr->atttypid;
+	*rettypmod = attr->atttypmod;
 
 	/*
 	 * If there are no rows selected, the result is a NULL of that type.
@@ -6030,7 +6047,8 @@ exec_move_row(PLpgSQL_execstate *estate,
 
 			var = (PLpgSQL_var *) (estate->datums[row->varnos[fnum]]);
 
-			while (anum < td_natts && tupdesc->attrs[anum]->attisdropped)
+			while (anum < td_natts &&
+				   TupleDescAttr(tupdesc, anum)->attisdropped)
 				anum++;			/* skip dropped column in tuple */
 
 			if (anum < td_natts)
@@ -6042,8 +6060,8 @@ exec_move_row(PLpgSQL_execstate *estate,
 					value = (Datum) 0;
 					isnull = true;
 				}
-				valtype = tupdesc->attrs[anum]->atttypid;
-				valtypmod = tupdesc->attrs[anum]->atttypmod;
+				valtype = TupleDescAttr(tupdesc, anum)->atttypid;
+				valtypmod = TupleDescAttr(tupdesc, anum)->atttypmod;
 				anum++;
 			}
 			else
@@ -6095,7 +6113,7 @@ make_tuple_from_row(PLpgSQL_execstate *estate,
 		Oid			fieldtypeid;
 		int32		fieldtypmod;
 
-		if (tupdesc->attrs[i]->attisdropped)
+		if (TupleDescAttr(tupdesc, i)->attisdropped)
 		{
 			nulls[i] = true;	/* leave the column as null */
 			continue;
@@ -6106,7 +6124,7 @@ make_tuple_from_row(PLpgSQL_execstate *estate,
 		exec_eval_datum(estate, estate->datums[row->varnos[i]],
 						&fieldtypeid, &fieldtypmod,
 						&dvalues[i], &nulls[i]);
-		if (fieldtypeid != tupdesc->attrs[i]->atttypid)
+		if (fieldtypeid != TupleDescAttr(tupdesc, i)->atttypid)
 			return NULL;
 		/* XXX should we insist on typmod match, too? */
 	}
diff --git a/src/pl/plpython/plpy_exec.c b/src/pl/plpython/plpy_exec.c
index c6938d00aa46bc15616754faf773cf1f9fbeb90f..26f61dd0f37b613f64fdfbb369a5725d56eca8be 100644
--- a/src/pl/plpython/plpy_exec.c
+++ b/src/pl/plpython/plpy_exec.c
@@ -950,6 +950,7 @@ PLy_modify_tuple(PLyProcedure *proc, PyObject *pltd, TriggerData *tdata,
 			char	   *plattstr;
 			int			attn;
 			PLyObToDatum *att;
+			Form_pg_attribute attr;
 
 			platt = PyList_GetItem(plkeys, i);
 			if (PyString_Check(platt))
@@ -982,11 +983,12 @@ PLy_modify_tuple(PLyProcedure *proc, PyObject *pltd, TriggerData *tdata,
 
 			Py_INCREF(plval);
 
+			attr = TupleDescAttr(tupdesc, attn - 1);
 			if (plval != Py_None)
 			{
 				modvalues[attn - 1] =
 					(att->func) (att,
-								 tupdesc->attrs[attn - 1]->atttypmod,
+								 attr->atttypmod,
 								 plval,
 								 false);
 				modnulls[attn - 1] = false;
@@ -997,7 +999,7 @@ PLy_modify_tuple(PLyProcedure *proc, PyObject *pltd, TriggerData *tdata,
 					InputFunctionCall(&att->typfunc,
 									  NULL,
 									  att->typioparam,
-									  tupdesc->attrs[attn - 1]->atttypmod);
+									  attr->atttypmod);
 				modnulls[attn - 1] = true;
 			}
 			modrepls[attn - 1] = true;
diff --git a/src/pl/plpython/plpy_resultobject.c b/src/pl/plpython/plpy_resultobject.c
index 077bde6dc358b6e0e7b5157bed9aec739c6219cf..098a366f6fa72ba123a6823ce2fc3363fd7a3c44 100644
--- a/src/pl/plpython/plpy_resultobject.c
+++ b/src/pl/plpython/plpy_resultobject.c
@@ -148,7 +148,11 @@ PLy_result_colnames(PyObject *self, PyObject *unused)
 
 	list = PyList_New(ob->tupdesc->natts);
 	for (i = 0; i < ob->tupdesc->natts; i++)
-		PyList_SET_ITEM(list, i, PyString_FromString(NameStr(ob->tupdesc->attrs[i]->attname)));
+	{
+		Form_pg_attribute attr = TupleDescAttr(ob->tupdesc, i);
+
+		PyList_SET_ITEM(list, i, PyString_FromString(NameStr(attr->attname)));
+	}
 
 	return list;
 }
@@ -168,7 +172,11 @@ PLy_result_coltypes(PyObject *self, PyObject *unused)
 
 	list = PyList_New(ob->tupdesc->natts);
 	for (i = 0; i < ob->tupdesc->natts; i++)
-		PyList_SET_ITEM(list, i, PyInt_FromLong(ob->tupdesc->attrs[i]->atttypid));
+	{
+		Form_pg_attribute attr = TupleDescAttr(ob->tupdesc, i);
+
+		PyList_SET_ITEM(list, i, PyInt_FromLong(attr->atttypid));
+	}
 
 	return list;
 }
@@ -188,7 +196,11 @@ PLy_result_coltypmods(PyObject *self, PyObject *unused)
 
 	list = PyList_New(ob->tupdesc->natts);
 	for (i = 0; i < ob->tupdesc->natts; i++)
-		PyList_SET_ITEM(list, i, PyInt_FromLong(ob->tupdesc->attrs[i]->atttypmod));
+	{
+		Form_pg_attribute attr = TupleDescAttr(ob->tupdesc, i);
+
+		PyList_SET_ITEM(list, i, PyInt_FromLong(attr->atttypmod));
+	}
 
 	return list;
 }
diff --git a/src/pl/plpython/plpy_typeio.c b/src/pl/plpython/plpy_typeio.c
index 91ddcaa7b9ce26ad16ebe7f20662a0150ca3c4e0..e4af8cc9ef0d7d91ca34bc2d71f301884ea1b70c 100644
--- a/src/pl/plpython/plpy_typeio.c
+++ b/src/pl/plpython/plpy_typeio.c
@@ -152,21 +152,21 @@ PLy_input_tuple_funcs(PLyTypeInfo *arg, TupleDesc desc)
 	for (i = 0; i < desc->natts; i++)
 	{
 		HeapTuple	typeTup;
+		Form_pg_attribute attr = TupleDescAttr(desc, i);
 
-		if (desc->attrs[i]->attisdropped)
+		if (attr->attisdropped)
 			continue;
 
-		if (arg->in.r.atts[i].typoid == desc->attrs[i]->atttypid)
+		if (arg->in.r.atts[i].typoid == attr->atttypid)
 			continue;			/* already set up this entry */
 
-		typeTup = SearchSysCache1(TYPEOID,
-								  ObjectIdGetDatum(desc->attrs[i]->atttypid));
+		typeTup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(attr->atttypid));
 		if (!HeapTupleIsValid(typeTup))
 			elog(ERROR, "cache lookup failed for type %u",
-				 desc->attrs[i]->atttypid);
+				 attr->atttypid);
 
 		PLy_input_datum_func2(&(arg->in.r.atts[i]), arg->mcxt,
-							  desc->attrs[i]->atttypid,
+							  attr->atttypid,
 							  typeTup,
 							  exec_ctx->curr_proc->langid,
 							  exec_ctx->curr_proc->trftypes);
@@ -224,18 +224,18 @@ PLy_output_tuple_funcs(PLyTypeInfo *arg, TupleDesc desc)
 	for (i = 0; i < desc->natts; i++)
 	{
 		HeapTuple	typeTup;
+		Form_pg_attribute attr = TupleDescAttr(desc, i);
 
-		if (desc->attrs[i]->attisdropped)
+		if (attr->attisdropped)
 			continue;
 
-		if (arg->out.r.atts[i].typoid == desc->attrs[i]->atttypid)
+		if (arg->out.r.atts[i].typoid == attr->atttypid)
 			continue;			/* already set up this entry */
 
-		typeTup = SearchSysCache1(TYPEOID,
-								  ObjectIdGetDatum(desc->attrs[i]->atttypid));
+		typeTup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(attr->atttypid));
 		if (!HeapTupleIsValid(typeTup))
 			elog(ERROR, "cache lookup failed for type %u",
-				 desc->attrs[i]->atttypid);
+				 attr->atttypid);
 
 		PLy_output_datum_func2(&(arg->out.r.atts[i]), arg->mcxt, typeTup,
 							   exec_ctx->curr_proc->langid,
@@ -306,11 +306,12 @@ PLyDict_FromTuple(PLyTypeInfo *info, HeapTuple tuple, TupleDesc desc)
 			Datum		vattr;
 			bool		is_null;
 			PyObject   *value;
+			Form_pg_attribute attr = TupleDescAttr(desc, i);
 
-			if (desc->attrs[i]->attisdropped)
+			if (attr->attisdropped)
 				continue;
 
-			key = NameStr(desc->attrs[i]->attname);
+			key = NameStr(attr->attname);
 			vattr = heap_getattr(tuple, (i + 1), desc, &is_null);
 
 			if (is_null || info->in.r.atts[i].func == NULL)
@@ -1183,15 +1184,16 @@ PLyMapping_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *mapping)
 		char	   *key;
 		PyObject   *volatile value;
 		PLyObToDatum *att;
+		Form_pg_attribute attr = TupleDescAttr(desc, i);
 
-		if (desc->attrs[i]->attisdropped)
+		if (attr->attisdropped)
 		{
 			values[i] = (Datum) 0;
 			nulls[i] = true;
 			continue;
 		}
 
-		key = NameStr(desc->attrs[i]->attname);
+		key = NameStr(attr->attname);
 		value = NULL;
 		att = &info->out.r.atts[i];
 		PG_TRY();
@@ -1256,7 +1258,7 @@ PLySequence_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *sequence)
 	idx = 0;
 	for (i = 0; i < desc->natts; i++)
 	{
-		if (!desc->attrs[i]->attisdropped)
+		if (!TupleDescAttr(desc, i)->attisdropped)
 			idx++;
 	}
 	if (PySequence_Length(sequence) != idx)
@@ -1277,7 +1279,7 @@ PLySequence_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *sequence)
 		PyObject   *volatile value;
 		PLyObToDatum *att;
 
-		if (desc->attrs[i]->attisdropped)
+		if (TupleDescAttr(desc, i)->attisdropped)
 		{
 			values[i] = (Datum) 0;
 			nulls[i] = true;
@@ -1346,15 +1348,16 @@ PLyGenericObject_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *object
 		char	   *key;
 		PyObject   *volatile value;
 		PLyObToDatum *att;
+		Form_pg_attribute attr = TupleDescAttr(desc, i);
 
-		if (desc->attrs[i]->attisdropped)
+		if (attr->attisdropped)
 		{
 			values[i] = (Datum) 0;
 			nulls[i] = true;
 			continue;
 		}
 
-		key = NameStr(desc->attrs[i]->attname);
+		key = NameStr(attr->attname);
 		value = NULL;
 		att = &info->out.r.atts[i];
 		PG_TRY();
diff --git a/src/pl/tcl/pltcl.c b/src/pl/tcl/pltcl.c
index ed494e1210875799c0fb9e5589642c3999c8db1d..09f87ec791638bcc2236fa8f182c978957e52bb0 100644
--- a/src/pl/tcl/pltcl.c
+++ b/src/pl/tcl/pltcl.c
@@ -1106,11 +1106,13 @@ pltcl_trigger_handler(PG_FUNCTION_ARGS, pltcl_call_state *call_state,
 		Tcl_ListObjAppendElement(NULL, tcl_trigtup, Tcl_NewObj());
 		for (i = 0; i < tupdesc->natts; i++)
 		{
-			if (tupdesc->attrs[i]->attisdropped)
+			Form_pg_attribute att = TupleDescAttr(tupdesc, i);
+
+			if (att->attisdropped)
 				Tcl_ListObjAppendElement(NULL, tcl_trigtup, Tcl_NewObj());
 			else
 				Tcl_ListObjAppendElement(NULL, tcl_trigtup,
-										 Tcl_NewStringObj(utf_e2u(NameStr(tupdesc->attrs[i]->attname)), -1));
+										 Tcl_NewStringObj(utf_e2u(NameStr(att->attname)), -1));
 		}
 		Tcl_ListObjAppendElement(NULL, tcl_cmd, tcl_trigtup);
 
@@ -2952,15 +2954,17 @@ pltcl_set_tuple_values(Tcl_Interp *interp, const char *arrayname,
 
 	for (i = 0; i < tupdesc->natts; i++)
 	{
+		Form_pg_attribute att = TupleDescAttr(tupdesc, i);
+
 		/* ignore dropped attributes */
-		if (tupdesc->attrs[i]->attisdropped)
+		if (att->attisdropped)
 			continue;
 
 		/************************************************************
 		 * Get the attribute name
 		 ************************************************************/
 		UTF_BEGIN;
-		attname = pstrdup(UTF_E2U(NameStr(tupdesc->attrs[i]->attname)));
+		attname = pstrdup(UTF_E2U(NameStr(att->attname)));
 		UTF_END;
 
 		/************************************************************
@@ -2978,8 +2982,7 @@ pltcl_set_tuple_values(Tcl_Interp *interp, const char *arrayname,
 		 ************************************************************/
 		if (!isnull)
 		{
-			getTypeOutputInfo(tupdesc->attrs[i]->atttypid,
-							  &typoutput, &typisvarlena);
+			getTypeOutputInfo(att->atttypid, &typoutput, &typisvarlena);
 			outputstr = OidOutputFunctionCall(typoutput, attr);
 			UTF_BEGIN;
 			Tcl_SetVar2Ex(interp, *arrptr, *nameptr,
@@ -3013,14 +3016,16 @@ pltcl_build_tuple_argument(HeapTuple tuple, TupleDesc tupdesc)
 
 	for (i = 0; i < tupdesc->natts; i++)
 	{
+		Form_pg_attribute att = TupleDescAttr(tupdesc, i);
+
 		/* ignore dropped attributes */
-		if (tupdesc->attrs[i]->attisdropped)
+		if (att->attisdropped)
 			continue;
 
 		/************************************************************
 		 * Get the attribute name
 		 ************************************************************/
-		attname = NameStr(tupdesc->attrs[i]->attname);
+		attname = NameStr(att->attname);
 
 		/************************************************************
 		 * Get the attributes value
@@ -3037,7 +3042,7 @@ pltcl_build_tuple_argument(HeapTuple tuple, TupleDesc tupdesc)
 		 ************************************************************/
 		if (!isnull)
 		{
-			getTypeOutputInfo(tupdesc->attrs[i]->atttypid,
+			getTypeOutputInfo(att->atttypid,
 							  &typoutput, &typisvarlena);
 			outputstr = OidOutputFunctionCall(typoutput, attr);
 			UTF_BEGIN;
diff --git a/src/test/regress/regress.c b/src/test/regress/regress.c
index b73bccec3d4fbf835a6bf9126e04bd553e55f2c9..3d33b36e66f43e58568b2a24d5f9340b73ac06ec 100644
--- a/src/test/regress/regress.c
+++ b/src/test/regress/regress.c
@@ -770,9 +770,9 @@ make_tuple_indirect(PG_FUNCTION_ARGS)
 		struct varatt_indirect redirect_pointer;
 
 		/* only work on existing, not-null varlenas */
-		if (tupdesc->attrs[i]->attisdropped ||
+		if (TupleDescAttr(tupdesc, i)->attisdropped ||
 			nulls[i] ||
-			tupdesc->attrs[i]->attlen != -1)
+			TupleDescAttr(tupdesc, i)->attlen != -1)
 			continue;
 
 		attr = (struct varlena *) DatumGetPointer(values[i]);