On Thu, 19 Aug 2021 at 14:58, Andres Freund <and...@anarazel.de> wrote:
>
> Hi,
>
> On 2021-08-19 14:47:42 +0200, Matthias van de Meent wrote:
> > I tried to implement this 'compact attribute access descriptor' a few
> > months ago in my effort to improve btree index performance.
>
> cool
>
>
> > The patch allocates an array of 'TupleAttrAlignData'-structs at the
> > end of the attrs-array, fills it with the correct data upon
> > TupleDesc-creation, and uses this TupleAttrAlign-data for constructing
> > and destructing tuples.
>
> > One main difference from what you described was that I used a union
> > for storing attbyval and attstorage, as the latter is only applicable
> > to attlen < 0, and the first only for attlen >= 0. This keeps the
> > whole structure in 8 bytes, whilst also being useable in both tuple
> > forming and deforming.
>
> That's why I just talked about the naive way - it's clearly possible to
> do better... ;)
>
>
> > I hope this can is useful, otherwise sorry for the noise.
>
> It is!

Great!

> I haven't looked at your patch in detail, but I suspect that one reason
> that you didn't see performance benefits is that you added overhead as
> well. The computation of the "compact" memory location now will need a
> few more instructions than before, and I suspect the compiler may not
> even be able to optimize out some of the redundant accesses in loops.
>
> It'd be interesting to see what you'd get if you stored the compact
> array as the flexible-array and stored a pointer to the "full" attrs
> array (while still keeping it allocated together).

Yes, I remember testing swapping the order of the compact array with the
FormData_pg_attribute array as well, with no clear results.

I think this can partially be attributed to the split access methods of the
data in the attribute descriptor: some of it is 'give me the name', some of
it is 'does this attribute exist, what type description does it have?'
(atttypid, attnum, atttypmod, , and others are only interested in the
physical representation information. Prioritizing some over the other might
work, but I think to make full use of that it'd need a lot of work.

> Another reason is that it looks like you didn't touch
> slot_deform_heap_tuple(), which is I think the hottest of the deforming
> routines...

That might be for normal operations, but I'm not certain that code is in
the hot path for (btree) indexing workloads, due to the relatively high
number of operations on each tuple whilst sorting, or finding an insertion
point or scan start point.

Anyway, after some digging I found the final state of this patch before I
stopped working on it, and after polishing it up a bit with your
suggestions it now passes check-world on the latest head (8d2d6ec7).


Kind regards,

Matthias van de Meent
From 9624034b6d23f7c5812b87bed705cbf2ae781d36 Mon Sep 17 00:00:00 2001
From: Matthias van de Meent <boekew...@gmail.com>
Date: Sun, 20 Jun 2021 21:16:33 +0200
Subject: [PATCH] Compact TupleDesc->attr access descriptors

Evolved from previous iterations, and includes some further work based
on comments from Andres Freund [0]

[0] 
https://www.postgresql.org/message-id/20210819125756.lhcp5wlppfv2r47d%40alap3.anarazel.de
---
 src/backend/access/common/heaptuple.c   | 95 +++++++++++++------------
 src/backend/access/common/indextuple.c  | 52 ++++++++------
 src/backend/access/common/relation.c    |  2 +
 src/backend/access/common/tupdesc.c     | 73 ++++++++++++++++++-
 src/backend/access/heap/heapam.c        |  6 +-
 src/backend/access/heap/heaptoast.c     | 12 ++--
 src/backend/access/nbtree/nbtutils.c    |  6 +-
 src/backend/access/spgist/spgdoinsert.c |  2 +-
 src/backend/access/spgist/spgutils.c    |  3 +
 src/backend/access/table/toast_helper.c |  6 +-
 src/backend/catalog/heap.c              | 10 +--
 src/backend/catalog/index.c             |  2 +
 src/backend/catalog/toasting.c          |  4 ++
 src/backend/executor/execTuples.c       | 10 +--
 src/backend/executor/nodeValuesscan.c   |  4 +-
 src/backend/utils/adt/expandedrecord.c  | 14 ++--
 src/backend/utils/adt/ri_triggers.c     |  2 +-
 src/backend/utils/cache/catcache.c      |  6 +-
 src/backend/utils/cache/relcache.c      | 18 ++++-
 src/include/access/htup_details.h       | 54 +++++++-------
 src/include/access/itup.h               |  9 +--
 src/include/access/tupdesc.h            | 42 +++++++++--
 src/include/access/tupmacs.h            |  2 +-
 src/include/catalog/pg_attribute.h      |  5 ++
 24 files changed, 297 insertions(+), 142 deletions(-)

diff --git a/src/backend/access/common/heaptuple.c 
b/src/backend/access/common/heaptuple.c
index 0b56b0fa5a..3600f88b25 100644
--- a/src/backend/access/common/heaptuple.c
+++ b/src/backend/access/common/heaptuple.c
@@ -123,19 +123,18 @@ heap_compute_data_size(TupleDesc tupleDesc,
        Size            data_length = 0;
        int                     i;
        int                     numberOfAttributes = tupleDesc->natts;
+       TupleAttrAlign att;
 
-       for (i = 0; i < numberOfAttributes; i++)
+       for (i = 0, att = TupleDescAttrAlign(tupleDesc, i); i < 
numberOfAttributes; i++, att++)
        {
                Datum           val;
-               Form_pg_attribute atti;
 
                if (isnull[i])
                        continue;
 
                val = values[i];
-               atti = TupleDescAttr(tupleDesc, i);
 
-               if (ATT_IS_PACKABLE(atti) &&
+               if (ATT_IS_PACKABLE(att) &&
                        VARATT_CAN_MAKE_SHORT(DatumGetPointer(val)))
                {
                        /*
@@ -144,21 +143,21 @@ heap_compute_data_size(TupleDesc tupleDesc,
                         */
                        data_length += 
VARATT_CONVERTED_SHORT_SIZE(DatumGetPointer(val));
                }
-               else if (atti->attlen == -1 &&
+               else if (att->attlen == -1 &&
                                 
VARATT_IS_EXTERNAL_EXPANDED(DatumGetPointer(val)))
                {
                        /*
                         * we want to flatten the expanded value so that the 
constructed
                         * tuple doesn't depend on it
                         */
-                       data_length = att_align_nominal(data_length, 
atti->attalign);
+                       data_length = att_align_nominal(data_length, 
att->attalign);
                        data_length += EOH_get_flat_size(DatumGetEOHP(val));
                }
                else
                {
-                       data_length = att_align_datum(data_length, 
atti->attalign,
-                                                                               
  atti->attlen, val);
-                       data_length = att_addlength_datum(data_length, 
atti->attlen,
+                       data_length = att_align_datum(data_length, 
att->attalign,
+                                                                               
  att->attlen, val);
+                       data_length = att_addlength_datum(data_length, 
att->attlen,
                                                                                
          val);
                }
        }
@@ -172,7 +171,7 @@ heap_compute_data_size(TupleDesc tupleDesc,
  * Fill in either a data value or a bit in the null bitmask
  */
 static inline void
-fill_val(Form_pg_attribute att,
+fill_val(TupleAttrAlign att,
                 bits8 **bit,
                 int *bitmask,
                 char **dataP,
@@ -211,7 +210,7 @@ fill_val(Form_pg_attribute att,
         * XXX we use the att_align macros on the pointer value itself, not on 
an
         * offset.  This is a bit of a hack.
         */
-       if (att->attbyval)
+       if (att->attbyval && att->attlen >= 0)
        {
                /* pass-by-value */
                data = (char *) att_align_nominal(data, att->attalign);
@@ -310,6 +309,7 @@ heap_fill_tuple(TupleDesc tupleDesc,
        int                     bitmask;
        int                     i;
        int                     numberOfAttributes = tupleDesc->natts;
+       TupleAttrAlign attr;
 
 #ifdef USE_ASSERT_CHECKING
        char       *start = data;
@@ -329,10 +329,8 @@ heap_fill_tuple(TupleDesc tupleDesc,
 
        *infomask &= ~(HEAP_HASNULL | HEAP_HASVARWIDTH | HEAP_HASEXTERNAL);
 
-       for (i = 0; i < numberOfAttributes; i++)
+       for (i = 0, attr = TupleDescAttrAlign(tupleDesc, i); i < 
numberOfAttributes; i++, attr++)
        {
-               Form_pg_attribute attr = TupleDescAttr(tupleDesc, i);
-
                fill_val(attr,
                                 bitP ? &bitP : NULL,
                                 &bitmask,
@@ -429,6 +427,7 @@ nocachegetattr(HeapTuple tuple,
        bits8      *bp = tup->t_bits;   /* ptr to null bitmap in tuple */
        bool            slow = false;   /* do we have to walk attrs? */
        int                     off;                    /* current offset 
within data */
+       TupleAttrAlign thisAttrAlign;
 
        /* ----------------
         *       Three cases:
@@ -474,13 +473,13 @@ nocachegetattr(HeapTuple tuple,
 
        if (!slow)
        {
-               Form_pg_attribute att;
+               TupleAttrAlign att;
 
                /*
                 * If we get here, there are no nulls up to and including the 
target
                 * attribute.  If we have a cached offset, we can use it.
                 */
-               att = TupleDescAttr(tupleDesc, attnum);
+               att = TupleDescAttrAlign(tupleDesc, attnum);
                if (att->attcacheoff >= 0)
                        return fetchatt(att, tp + att->attcacheoff);
 
@@ -492,10 +491,11 @@ nocachegetattr(HeapTuple tuple,
                if (HeapTupleHasVarWidth(tuple))
                {
                        int                     j;
+                       att = TupleDescAttrAlign(tupleDesc, 0);
 
-                       for (j = 0; j <= attnum; j++)
+                       for (j = 0; j <= attnum; j++, att++)
                        {
-                               if (TupleDescAttr(tupleDesc, j)->attlen <= 0)
+                               if (att->attlen <= 0)
                                {
                                        slow = true;
                                        break;
@@ -508,6 +508,7 @@ nocachegetattr(HeapTuple tuple,
        {
                int                     natts = tupleDesc->natts;
                int                     j = 1;
+               TupleAttrAlign att;
 
                /*
                 * If we get here, we have a tuple with no nulls or var-widths 
up to
@@ -518,19 +519,18 @@ nocachegetattr(HeapTuple tuple,
                 * fixed-width columns, in hope of avoiding future visits to 
this
                 * routine.
                 */
-               TupleDescAttr(tupleDesc, 0)->attcacheoff = 0;
+               att = TupleDescAttrAlign(tupleDesc, 0);
+               att->attcacheoff = 0;
+               att++;
 
                /* we might have set some offsets in the slow path previously */
-               while (j < natts && TupleDescAttr(tupleDesc, j)->attcacheoff > 
0)
-                       j++;
+               while (j < natts && att->attcacheoff > 0)
+                       j++, att++;
 
-               off = TupleDescAttr(tupleDesc, j - 1)->attcacheoff +
-                       TupleDescAttr(tupleDesc, j - 1)->attlen;
+               off = (att - 1)->attcacheoff + (att - 1)->attlen;
 
-               for (; j < natts; j++)
+               for (; j < natts; j++, att++)
                {
-                       Form_pg_attribute att = TupleDescAttr(tupleDesc, j);
-
                        if (att->attlen <= 0)
                                break;
 
@@ -543,12 +543,14 @@ nocachegetattr(HeapTuple tuple,
 
                Assert(j > attnum);
 
-               off = TupleDescAttr(tupleDesc, attnum)->attcacheoff;
+               thisAttrAlign = TupleDescAttrAlign(tupleDesc, attnum);
+               off = thisAttrAlign->attcacheoff;
        }
        else
        {
                bool            usecache = true;
                int                     i;
+               TupleAttrAlign att = TupleDescAttrAlign(tupleDesc, 0);
 
                /*
                 * Now we know that we have to walk the tuple CAREFULLY.  But 
we still
@@ -561,10 +563,8 @@ nocachegetattr(HeapTuple tuple,
                 * attcacheoff until we reach either a null or a var-width 
attribute.
                 */
                off = 0;
-               for (i = 0;; i++)               /* loop exit is at "break" */
+               for (i = 0;; i++, att++)                /* loop exit is at 
"break" */
                {
-                       Form_pg_attribute att = TupleDescAttr(tupleDesc, i);
-
                        if (HeapTupleHasNulls(tuple) && att_isnull(i, bp))
                        {
                                usecache = false;
@@ -602,7 +602,10 @@ nocachegetattr(HeapTuple tuple,
                        }
 
                        if (i == attnum)
+                       {
+                               thisAttrAlign = att;
                                break;
+                       }
 
                        off = att_addlength_pointer(off, att->attlen, tp + off);
 
@@ -611,7 +614,7 @@ nocachegetattr(HeapTuple tuple,
                }
        }
 
-       return fetchatt(TupleDescAttr(tupleDesc, attnum), tp + off);
+       return fetchatt(thisAttrAlign, tp + off);
 }
 
 /* ----------------
@@ -925,7 +928,7 @@ expand_tuple(HeapTuple *targetHeapTuple,
        for (attnum = sourceNatts; attnum < natts; attnum++)
        {
 
-               Form_pg_attribute attr = TupleDescAttr(tupleDesc, attnum);
+               TupleAttrAlign attr = TupleDescAttrAlign(tupleDesc, attnum);
 
                if (attrmiss && attrmiss[attnum].am_present)
                {
@@ -1258,6 +1261,7 @@ heap_deform_tuple(HeapTuple tuple, TupleDesc tupleDesc,
        uint32          off;                    /* offset in tuple data */
        bits8      *bp = tup->t_bits;   /* ptr to null bitmap in tuple */
        bool            slow = false;   /* can we use/set attcacheoff? */
+       TupleAttrAlign att;
 
        natts = HeapTupleHeaderGetNatts(tup);
 
@@ -1272,10 +1276,9 @@ heap_deform_tuple(HeapTuple tuple, TupleDesc tupleDesc,
 
        off = 0;
 
-       for (attnum = 0; attnum < natts; attnum++)
+       for (attnum = 0, att = TupleDescAttrAlign(tupleDesc, attnum);
+                attnum < natts; attnum++, att++)
        {
-               Form_pg_attribute thisatt = TupleDescAttr(tupleDesc, attnum);
-
                if (hasnulls && att_isnull(attnum, bp))
                {
                        values[attnum] = (Datum) 0;
@@ -1286,9 +1289,9 @@ heap_deform_tuple(HeapTuple tuple, TupleDesc tupleDesc,
 
                isnull[attnum] = false;
 
-               if (!slow && thisatt->attcacheoff >= 0)
-                       off = thisatt->attcacheoff;
-               else if (thisatt->attlen == -1)
+               if (!slow && att->attcacheoff >= 0)
+                       off = att->attcacheoff;
+               else if (att->attlen == -1)
                {
                        /*
                         * We can only cache the offset for a varlena attribute 
if the
@@ -1297,11 +1300,11 @@ heap_deform_tuple(HeapTuple tuple, TupleDesc tupleDesc,
                         * an aligned or unaligned value.
                         */
                        if (!slow &&
-                               off == att_align_nominal(off, 
thisatt->attalign))
-                               thisatt->attcacheoff = off;
+                               off == att_align_nominal(off, att->attalign))
+                               att->attcacheoff = off;
                        else
                        {
-                               off = att_align_pointer(off, thisatt->attalign, 
-1,
+                               off = att_align_pointer(off, att->attalign, -1,
                                                                                
tp + off);
                                slow = true;
                        }
@@ -1309,17 +1312,17 @@ heap_deform_tuple(HeapTuple tuple, TupleDesc tupleDesc,
                else
                {
                        /* not varlena, so safe to use att_align_nominal */
-                       off = att_align_nominal(off, thisatt->attalign);
+                       off = att_align_nominal(off, att->attalign);
 
                        if (!slow)
-                               thisatt->attcacheoff = off;
+                               att->attcacheoff = off;
                }
 
-               values[attnum] = fetchatt(thisatt, tp + off);
+               values[attnum] = fetchatt(att, tp + off);
 
-               off = att_addlength_pointer(off, thisatt->attlen, tp + off);
+               off = att_addlength_pointer(off, att->attlen, tp + off);
 
-               if (thisatt->attlen <= 0)
+               if (att->attlen <= 0)
                        slow = true;            /* can't use attcacheoff 
anymore */
        }
 
diff --git a/src/backend/access/common/indextuple.c 
b/src/backend/access/common/indextuple.c
index 8df882da7a..b2c08216db 100644
--- a/src/backend/access/common/indextuple.c
+++ b/src/backend/access/common/indextuple.c
@@ -228,7 +228,7 @@ nocache_index_getattr(IndexTuple tup,
        bool            slow = false;   /* do we have to walk attrs? */
        int                     data_off;               /* tuple data offset */
        int                     off;                    /* current offset 
within data */
-
+       TupleAttrAlign thisAttrAlign;
        /* ----------------
         *       Three cases:
         *
@@ -242,6 +242,8 @@ nocache_index_getattr(IndexTuple tup,
 
        attnum--;
 
+       Assert(TupleDescAttrAlignsIsConsistent(tupleDesc));
+
        if (IndexTupleHasNulls(tup))
        {
                /*
@@ -284,13 +286,13 @@ nocache_index_getattr(IndexTuple tup,
 
        if (!slow)
        {
-               Form_pg_attribute att;
+               TupleAttrAlign att;
 
                /*
                 * If we get here, there are no nulls up to and including the 
target
                 * attribute.  If we have a cached offset, we can use it.
                 */
-               att = TupleDescAttr(tupleDesc, attnum);
+               att = TupleDescAttrAlign(tupleDesc, attnum);
                if (att->attcacheoff >= 0)
                        return fetchatt(att, tp + att->attcacheoff);
 
@@ -302,10 +304,11 @@ nocache_index_getattr(IndexTuple tup,
                if (IndexTupleHasVarwidths(tup))
                {
                        int                     j;
+                       att = TupleDescAttrAlign(tupleDesc, 0);
 
-                       for (j = 0; j <= attnum; j++)
+                       for (j = 0; j <= attnum; j++, att++)
                        {
-                               if (TupleDescAttr(tupleDesc, j)->attlen <= 0)
+                               if (att->attlen <= 0)
                                {
                                        slow = true;
                                        break;
@@ -318,6 +321,7 @@ nocache_index_getattr(IndexTuple tup,
        {
                int                     natts = tupleDesc->natts;
                int                     j = 1;
+               TupleAttrAlign att;
 
                /*
                 * If we get here, we have a tuple with no nulls or var-widths 
up to
@@ -328,19 +332,18 @@ nocache_index_getattr(IndexTuple tup,
                 * fixed-width columns, in hope of avoiding future visits to 
this
                 * routine.
                 */
-               TupleDescAttr(tupleDesc, 0)->attcacheoff = 0;
+               att = TupleDescAttrAlign(tupleDesc, 0);
+               att->attcacheoff = 0;
+               att++;
 
                /* we might have set some offsets in the slow path previously */
-               while (j < natts && TupleDescAttr(tupleDesc, j)->attcacheoff > 
0)
-                       j++;
+               while (j < natts && att->attcacheoff > 0)
+                       j++, att++;
 
-               off = TupleDescAttr(tupleDesc, j - 1)->attcacheoff +
-                       TupleDescAttr(tupleDesc, j - 1)->attlen;
+               off = (att - 1)->attcacheoff + (att - 1)->attlen;
 
-               for (; j < natts; j++)
+               for (; j < natts; j++, att++)
                {
-                       Form_pg_attribute att = TupleDescAttr(tupleDesc, j);
-
                        if (att->attlen <= 0)
                                break;
 
@@ -353,13 +356,14 @@ nocache_index_getattr(IndexTuple tup,
 
                Assert(j > attnum);
 
-               off = TupleDescAttr(tupleDesc, attnum)->attcacheoff;
+               thisAttrAlign = TupleDescAttrAlign(tupleDesc, attnum);
+               off = thisAttrAlign->attcacheoff;
        }
        else
        {
                bool            usecache = true;
                int                     i;
-
+               TupleAttrAlign att;
                /*
                 * Now we know that we have to walk the tuple CAREFULLY.  But 
we still
                 * might be able to cache some offsets for next time.
@@ -371,10 +375,10 @@ nocache_index_getattr(IndexTuple tup,
                 * attcacheoff until we reach either a null or a var-width 
attribute.
                 */
                off = 0;
-               for (i = 0;; i++)               /* loop exit is at "break" */
-               {
-                       Form_pg_attribute att = TupleDescAttr(tupleDesc, i);
+               att = TupleDescAttrAlign(tupleDesc, 0);
 
+               for (i = 0;; i++, att++)                /* loop exit is at 
"break" */
+               {
                        if (IndexTupleHasNulls(tup) && att_isnull(i, bp))
                        {
                                usecache = false;
@@ -412,7 +416,10 @@ nocache_index_getattr(IndexTuple tup,
                        }
 
                        if (i == attnum)
+                       {
+                               thisAttrAlign = att;
                                break;
+                       }
 
                        off = att_addlength_pointer(off, att->attlen, tp + off);
 
@@ -421,7 +428,7 @@ nocache_index_getattr(IndexTuple tup,
                }
        }
 
-       return fetchatt(TupleDescAttr(tupleDesc, attnum), tp + off);
+       return fetchatt(thisAttrAlign, tp + off);
 }
 
 /*
@@ -465,14 +472,14 @@ index_deform_tuple_internal(TupleDesc tupleDescriptor,
        int                     attnum;
        int                     off = 0;                /* offset in tuple data 
*/
        bool            slow = false;   /* can we use/set attcacheoff? */
+       TupleAttrAlign thisatt;
 
        /* Assert to protect callers who allocate fixed-size arrays */
        Assert(natts <= INDEX_MAX_KEYS);
 
-       for (attnum = 0; attnum < natts; attnum++)
+       for (attnum = 0, thisatt = TupleDescAttrAlign(tupleDescriptor, attnum);
+                attnum < natts; attnum++, thisatt++)
        {
-               Form_pg_attribute thisatt = TupleDescAttr(tupleDescriptor, 
attnum);
-
                if (hasnulls && att_isnull(attnum, bp))
                {
                        values[attnum] = (Datum) 0;
@@ -572,6 +579,7 @@ index_truncate_tuple(TupleDesc sourceDescriptor, IndexTuple 
source,
        truncdesc = palloc(TupleDescSize(sourceDescriptor));
        TupleDescCopy(truncdesc, sourceDescriptor);
        truncdesc->natts = leavenatts;
+       TupleDescInitAttrAligns(truncdesc);
 
        /* Deform, form copy of tuple with fewer attributes */
        index_deform_tuple(source, truncdesc, values, isnull);
diff --git a/src/backend/access/common/relation.c 
b/src/backend/access/common/relation.c
index 632d13c1ea..629d60b947 100644
--- a/src/backend/access/common/relation.c
+++ b/src/backend/access/common/relation.c
@@ -61,6 +61,8 @@ relation_open(Oid relationId, LOCKMODE lockmode)
        if (!RelationIsValid(r))
                elog(ERROR, "could not open relation with OID %u", relationId);
 
+       Assert(TupleDescAttrAlignsIsConsistent(r->rd_att));
+
        /*
         * If we didn't get the lock ourselves, assert that caller holds one,
         * except in bootstrap mode where no locks are used.
diff --git a/src/backend/access/common/tupdesc.c 
b/src/backend/access/common/tupdesc.c
index 4c63bd4dc6..ecc4160dd9 100644
--- a/src/backend/access/common/tupdesc.c
+++ b/src/backend/access/common/tupdesc.c
@@ -63,7 +63,8 @@ CreateTemplateTupleDesc(int natts)
         * could be less due to trailing padding, although with the current
         * definition of pg_attribute there probably isn't any padding.
         */
-       desc = (TupleDesc) palloc(offsetof(struct TupleDescData, attrs) +
+       desc = (TupleDesc) palloc(MAXALIGN((offsetof(struct TupleDescData, 
attrs)) +
+                                                                               
           natts * sizeof(TupleAttrAlignData)) +
                                                          natts * 
sizeof(FormData_pg_attribute));
 
        /*
@@ -75,6 +76,9 @@ CreateTemplateTupleDesc(int natts)
        desc->tdtypmod = -1;
        desc->tdrefcount = -1;          /* assume not reference-counted */
 
+       desc->attroff = MAXALIGN((offsetof(struct TupleDescData, attrs)) +
+                                                        natts * 
sizeof(TupleAttrAlignData));
+
        return desc;
 }
 
@@ -97,6 +101,8 @@ CreateTupleDesc(int natts, Form_pg_attribute *attrs)
        for (i = 0; i < natts; ++i)
                memcpy(TupleDescAttr(desc, i), attrs[i], 
ATTRIBUTE_FIXED_PART_SIZE);
 
+       TupleDescInitAttrAligns(desc);
+
        return desc;
 }
 
@@ -135,6 +141,8 @@ CreateTupleDescCopy(TupleDesc tupdesc)
                att->attgenerated = '\0';
        }
 
+       TupleDescInitAttrAligns(desc);
+
        /* We can copy the tuple type identification, too */
        desc->tdtypeid = tupdesc->tdtypeid;
        desc->tdtypmod = tupdesc->tdtypmod;
@@ -210,6 +218,10 @@ CreateTupleDescCopyConstr(TupleDesc tupdesc)
                desc->constr = cpy;
        }
 
+       memcpy(TupleDescAttrAlign(desc, 0),
+                  TupleDescAttrAlign(tupdesc, 0),
+                  tupdesc->natts * sizeof(TupleAttrAlignData));
+
        /* We can copy the tuple type identification, too */
        desc->tdtypeid = tupdesc->tdtypeid;
        desc->tdtypmod = tupdesc->tdtypmod;
@@ -300,6 +312,10 @@ TupleDescCopyEntry(TupleDesc dst, AttrNumber dstAttno,
        dstAtt->atthasmissing = false;
        dstAtt->attidentity = '\0';
        dstAtt->attgenerated = '\0';
+       
+       memcpy(TupleDescAttrAlign(dst, dstAttno - 1),
+                  TupleDescAttrAlign(src, srcAttno - 1),
+                  sizeof(TupleAttrAlignData));
 }
 
 /*
@@ -645,6 +661,8 @@ TupleDescInitEntry(TupleDesc desc,
        att->attcompression = InvalidCompressionMethod;
        att->attcollation = typeForm->typcollation;
 
+       CopyAttrToAlign(att, TupleDescAttrAlign(desc, attributeNumber - 1));
+
        ReleaseSysCache(tuple);
 }
 
@@ -662,6 +680,7 @@ TupleDescInitBuiltinEntry(TupleDesc desc,
                                                  int attdim)
 {
        Form_pg_attribute att;
+       TupleAttrAlign attrAlign;
 
        /* sanity checks */
        AssertArg(PointerIsValid(desc));
@@ -742,6 +761,10 @@ TupleDescInitBuiltinEntry(TupleDesc desc,
                default:
                        elog(ERROR, "unsupported type %u", oidtypeid);
        }
+
+       attrAlign = TupleDescAttrAlign(desc, attributeNumber - 1);
+
+       CopyAttrToAlign(att, attrAlign);
 }
 
 /*
@@ -765,6 +788,49 @@ TupleDescInitEntryCollation(TupleDesc desc,
        TupleDescAttr(desc, attributeNumber - 1)->attcollation = collationid;
 }
 
+/*
+ * TupleDescInitAttrAligns
+ *
+ * Initialize the attraligns field of the TupleDesc with data from
+ * tupledesc->attrs.
+ */
+void
+TupleDescInitAttrAligns(TupleDesc desc)
+{
+       for (int i = 0; i < desc->natts; i++)
+       {
+               Form_pg_attribute attr = TupleDescAttr(desc, i);
+               TupleAttrAlign attrAlign = TupleDescAttrAlign(desc, i);
+               CopyAttrToAlign(attr, attrAlign);
+       }
+}
+
+bool
+TupleDescAttrAlignsIsConsistent(TupleDesc desc)
+{
+       for (int i = 0; i < desc->natts; i++)
+       {
+               Form_pg_attribute attr = TupleDescAttr(desc, i);
+               TupleAttrAlign attrAlign = TupleDescAttrAlign(desc, i);
+               if (!(attrAlign->attcacheoff == attr->attcacheoff || 
attrAlign->attcacheoff == -1 || attr->attcacheoff == -1))
+                       return false;
+               if (attrAlign->attlen != attr->attlen)
+                       return false;
+               if (attrAlign->attalign != attr->attalign)
+                       return false;
+               if (attrAlign->attlen < 0)
+               {
+                       if (attrAlign->attstorage != attr->attstorage)
+                               return false;
+               }
+               else
+               {
+                       if (attrAlign->attbyval != attr->attbyval)
+                               return false;
+               }
+       }
+       return true;
+}
 
 /*
  * BuildDescForRelation
@@ -801,6 +867,7 @@ BuildDescForRelation(List *schema)
                ColumnDef  *entry = lfirst(l);
                AclResult       aclresult;
                Form_pg_attribute att;
+               TupleAttrAlign align;
 
                /*
                 * for each entry in the list, get the name and type 
information from
@@ -828,11 +895,15 @@ BuildDescForRelation(List *schema)
                TupleDescInitEntry(desc, attnum, attname,
                                                   atttypid, atttypmod, attdim);
                att = TupleDescAttr(desc, attnum - 1);
+               align = TupleDescAttrAlign(desc, attnum - 1);
 
                /* Override TupleDescInitEntry's settings as requested */
                TupleDescInitEntryCollation(desc, attnum, attcollation);
                if (entry->storage)
+               {
                        att->attstorage = entry->storage;
+                       align->attstorage = entry->storage;
+               }
 
                /* Fill in additional stuff not handled by TupleDescInitEntry */
                att->attnotnull = entry->is_not_null;
diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index 2433998f39..59f8f97697 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -1144,11 +1144,11 @@ fastgetattr(HeapTuple tup, int attnum, TupleDesc 
tupleDesc,
                         (*(isnull) = false),
                         HeapTupleNoNulls(tup) ?
                         (
-                         TupleDescAttr((tupleDesc), (attnum) - 1)->attcacheoff 
>= 0 ?
+                         TupleDescAttrAlign((tupleDesc), (attnum) - 
1)->attcacheoff >= 0 ?
                          (
-                          fetchatt(TupleDescAttr((tupleDesc), (attnum) - 1),
+                          fetchatt(TupleDescAttrAlign((tupleDesc), (attnum) - 
1),
                                                (char *) (tup)->t_data + 
(tup)->t_data->t_hoff +
-                                               TupleDescAttr((tupleDesc), 
(attnum) - 1)->attcacheoff)
+                                               TupleDescAttrAlign((tupleDesc), 
(attnum) - 1)->attcacheoff)
                           )
                          :
                          nocachegetattr((tup), (attnum), (tupleDesc))
diff --git a/src/backend/access/heap/heaptoast.c 
b/src/backend/access/heap/heaptoast.c
index 55bbe1d584..e5fcbe15b8 100644
--- a/src/backend/access/heap/heaptoast.c
+++ b/src/backend/access/heap/heaptoast.c
@@ -190,10 +190,14 @@ heap_toast_insert_or_update(Relation rel, HeapTuple 
newtup, HeapTuple oldtup,
                if (biggest_attno < 0)
                        break;
 
+               Assert(TupleDescAttrAlign(tupleDesc, biggest_attno)->attlen < 
0);
+
                /*
                 * Attempt to compress it inline, if it has attstorage EXTENDED
+                *
+                * Safe to use attstorage, attlen < 0
                 */
-               if (TupleDescAttr(tupleDesc, biggest_attno)->attstorage == 
TYPSTORAGE_EXTENDED)
+               if (TupleDescAttrAlign(tupleDesc, biggest_attno)->attstorage == 
TYPSTORAGE_EXTENDED)
                        toast_tuple_try_compression(&ttc, biggest_attno);
                else
                {
@@ -369,7 +373,7 @@ toast_flatten_tuple(HeapTuple tup, TupleDesc tupleDesc)
                /*
                 * Look at non-null varlena attributes
                 */
-               if (!toast_isnull[i] && TupleDescAttr(tupleDesc, i)->attlen == 
-1)
+               if (!toast_isnull[i] && TupleDescAttrAlign(tupleDesc, 
i)->attlen == -1)
                {
                        struct varlena *new_value;
 
@@ -483,7 +487,7 @@ toast_flatten_tuple_to_datum(HeapTupleHeader tup,
                 */
                if (toast_isnull[i])
                        has_nulls = true;
-               else if (TupleDescAttr(tupleDesc, i)->attlen == -1)
+               else if (TupleDescAttrAlign(tupleDesc, i)->attlen == -1)
                {
                        struct varlena *new_value;
 
@@ -584,7 +588,7 @@ toast_build_flattened_tuple(TupleDesc tupleDesc,
                /*
                 * Look at non-null varlena attributes
                 */
-               if (!isnull[i] && TupleDescAttr(tupleDesc, i)->attlen == -1)
+               if (!isnull[i] && TupleDescAttrAlign(tupleDesc, i)->attlen == 
-1)
                {
                        struct varlena *new_value;
 
diff --git a/src/backend/access/nbtree/nbtutils.c 
b/src/backend/access/nbtree/nbtutils.c
index d524310723..49611bd81f 100644
--- a/src/backend/access/nbtree/nbtutils.c
+++ b/src/backend/access/nbtree/nbtutils.c
@@ -2429,17 +2429,17 @@ _bt_keep_natts_fast(Relation rel, IndexTuple lastleft, 
IndexTuple firstright)
                                        datum2;
                bool            isNull1,
                                        isNull2;
-               Form_pg_attribute att;
+               TupleAttrAlign att;
 
                datum1 = index_getattr(lastleft, attnum, itupdesc, &isNull1);
                datum2 = index_getattr(firstright, attnum, itupdesc, &isNull2);
-               att = TupleDescAttr(itupdesc, attnum - 1);
+               att = TupleDescAttrAlign(itupdesc, attnum - 1);
 
                if (isNull1 != isNull2)
                        break;
 
                if (!isNull1 &&
-                       !datum_image_eq(datum1, datum2, att->attbyval, 
att->attlen))
+                       !datum_image_eq(datum1, datum2, att->attlen >= 0 && 
att->attbyval, att->attlen))
                        break;
 
                keepnatts++;
diff --git a/src/backend/access/spgist/spgdoinsert.c 
b/src/backend/access/spgist/spgdoinsert.c
index 70557bcf3d..357320073d 100644
--- a/src/backend/access/spgist/spgdoinsert.c
+++ b/src/backend/access/spgist/spgdoinsert.c
@@ -1973,7 +1973,7 @@ spgdoinsert(Relation index, SpGistState *state,
        {
                if (!isnulls[i])
                {
-                       if (TupleDescAttr(leafDescriptor, i)->attlen == -1)
+                       if (TupleDescAttrAlign(leafDescriptor, i)->attlen == -1)
                                leafDatums[i] = 
PointerGetDatum(PG_DETOAST_DATUM(datums[i]));
                        else
                                leafDatums[i] = datums[i];
diff --git a/src/backend/access/spgist/spgutils.c 
b/src/backend/access/spgist/spgutils.c
index 03a9cd36e6..190978f403 100644
--- a/src/backend/access/spgist/spgutils.c
+++ b/src/backend/access/spgist/spgutils.c
@@ -306,9 +306,12 @@ getSpGistTupleDesc(Relation index, SpGistTypeDesc *keyType)
                /* We shouldn't need to bother with making these valid: */
                att->attcompression = InvalidCompressionMethod;
                att->attcollation = InvalidOid;
+
                /* In case we changed typlen, we'd better reset following 
offsets */
                for (int i = spgFirstIncludeColumn; i < outTupDesc->natts; i++)
                        TupleDescAttr(outTupDesc, i)->attcacheoff = -1;
+
+               TupleDescInitAttrAligns(outTupDesc);
        }
        return outTupDesc;
 }
diff --git a/src/backend/access/table/toast_helper.c 
b/src/backend/access/table/toast_helper.c
index 53f78f9c3e..cfe2fb4260 100644
--- a/src/backend/access/table/toast_helper.c
+++ b/src/backend/access/table/toast_helper.c
@@ -193,7 +193,7 @@ toast_tuple_find_biggest_attribute(ToastTupleContext *ttc,
 
        for (i = 0; i < numAttrs; i++)
        {
-               Form_pg_attribute att = TupleDescAttr(tupleDesc, i);
+               TupleAttrAlign att = TupleDescAttrAlign(tupleDesc, i);
 
                if ((ttc->ttc_attr[i].tai_colflags & skip_colflags) != 0)
                        continue;
@@ -202,6 +202,8 @@ toast_tuple_find_biggest_attribute(ToastTupleContext *ttc,
                if (for_compression &&
                        
VARATT_IS_COMPRESSED(DatumGetPointer(ttc->ttc_values[i])))
                        continue;
+               if (att->attlen >= 0)
+                       continue;                       /* can't compress 
fixed-size attributes */
                if (check_main && att->attstorage != TYPSTORAGE_MAIN)
                        continue;
                if (!check_main && att->attstorage != TYPSTORAGE_EXTENDED &&
@@ -324,7 +326,7 @@ toast_delete_external(Relation rel, Datum *values, bool 
*isnull,
 
        for (i = 0; i < numAttrs; i++)
        {
-               if (TupleDescAttr(tupleDesc, i)->attlen == -1)
+               if (TupleDescAttrAlign(tupleDesc, i)->attlen == -1)
                {
                        Datum           value = values[i];
 
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 83746d3fd9..bd688e4156 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -859,7 +859,7 @@ AddNewAttributeTuples(Oid new_rel_oid,
 
        /* set stats detail level to a sane default */
        for (int i = 0; i < natts; i++)
-               tupdesc->attrs[i].attstattarget = -1;
+               TupleDescAttr(tupdesc, i)->attstattarget = -1;
        InsertPgAttributeTuples(rel, tupdesc, new_rel_oid, NULL, indstate);
 
        /* add dependencies on their datatypes and collations */
@@ -868,15 +868,15 @@ AddNewAttributeTuples(Oid new_rel_oid,
                /* Add dependency info */
                ObjectAddressSubSet(myself, RelationRelationId, new_rel_oid, i 
+ 1);
                ObjectAddressSet(referenced, TypeRelationId,
-                                                tupdesc->attrs[i].atttypid);
+                                                TupleDescAttr(tupdesc, 
i)->atttypid);
                recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
 
                /* The default collation is pinned, so don't bother recording 
it */
-               if (OidIsValid(tupdesc->attrs[i].attcollation) &&
-                       tupdesc->attrs[i].attcollation != DEFAULT_COLLATION_OID)
+               if (OidIsValid(TupleDescAttr(tupdesc, i)->attcollation) &&
+                               TupleDescAttr(tupdesc, i)->attcollation != 
DEFAULT_COLLATION_OID)
                {
                        ObjectAddressSet(referenced, CollationRelationId,
-                                                        
tupdesc->attrs[i].attcollation);
+                                                        TupleDescAttr(tupdesc, 
i)->attcollation);
                        recordDependencyOn(&myself, &referenced, 
DEPENDENCY_NORMAL);
                }
        }
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 26bfa74ce7..b96a4fccf1 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -471,6 +471,8 @@ ConstructTupleDescriptor(Relation heapRelation,
                }
        }
 
+       TupleDescInitAttrAligns(indexTupDesc);
+
        pfree(amroutine);
 
        return indexTupDesc;
diff --git a/src/backend/catalog/toasting.c b/src/backend/catalog/toasting.c
index 147b5abc19..efe7b7be26 100644
--- a/src/backend/catalog/toasting.c
+++ b/src/backend/catalog/toasting.c
@@ -219,6 +219,10 @@ create_toast_table(Relation rel, Oid toastOid, Oid 
toastIndexOid,
        TupleDescAttr(tupdesc, 1)->attstorage = TYPSTORAGE_PLAIN;
        TupleDescAttr(tupdesc, 2)->attstorage = TYPSTORAGE_PLAIN;
 
+       CopyAttrToAlign(TupleDescAttr(tupdesc, 0), TupleDescAttrAlign(tupdesc, 
0));
+       CopyAttrToAlign(TupleDescAttr(tupdesc, 1), TupleDescAttrAlign(tupdesc, 
1));
+       CopyAttrToAlign(TupleDescAttr(tupdesc, 2), TupleDescAttrAlign(tupdesc, 
2));
+
        /* Toast field should not be compressed */
        TupleDescAttr(tupdesc, 0)->attcompression = InvalidCompressionMethod;
        TupleDescAttr(tupdesc, 1)->attcompression = InvalidCompressionMethod;
diff --git a/src/backend/executor/execTuples.c 
b/src/backend/executor/execTuples.c
index f447802843..29e978b7d3 100644
--- a/src/backend/executor/execTuples.c
+++ b/src/backend/executor/execTuples.c
@@ -170,10 +170,10 @@ tts_virtual_materialize(TupleTableSlot *slot)
        /* compute size of memory required */
        for (int natt = 0; natt < desc->natts; natt++)
        {
-               Form_pg_attribute att = TupleDescAttr(desc, natt);
+               TupleAttrAlign att = TupleDescAttrAlign(desc, natt);
                Datum           val;
 
-               if (att->attbyval || slot->tts_isnull[natt])
+               if ((att->attlen >= 0 && att->attbyval) || 
slot->tts_isnull[natt])
                        continue;
 
                val = slot->tts_values[natt];
@@ -206,10 +206,10 @@ tts_virtual_materialize(TupleTableSlot *slot)
        /* and copy all attributes into the pre-allocated space */
        for (int natt = 0; natt < desc->natts; natt++)
        {
-               Form_pg_attribute att = TupleDescAttr(desc, natt);
+               TupleAttrAlign att = TupleDescAttrAlign(desc, natt);
                Datum           val;
 
-               if (att->attbyval || slot->tts_isnull[natt])
+               if ((att->attlen >= 0 && att->attbyval) || 
slot->tts_isnull[natt])
                        continue;
 
                val = slot->tts_values[natt];
@@ -961,7 +961,7 @@ slot_deform_heap_tuple(TupleTableSlot *slot, HeapTuple 
tuple, uint32 *offp,
 
        for (; attnum < natts; attnum++)
        {
-               Form_pg_attribute thisatt = TupleDescAttr(tupleDesc, attnum);
+               TupleAttrAlign thisatt = TupleDescAttrAlign(tupleDesc, attnum);
 
                if (hasnulls && att_isnull(attnum, bp))
                {
diff --git a/src/backend/executor/nodeValuesscan.c 
b/src/backend/executor/nodeValuesscan.c
index 00bd1271e4..b1cb02da07 100644
--- a/src/backend/executor/nodeValuesscan.c
+++ b/src/backend/executor/nodeValuesscan.c
@@ -143,8 +143,8 @@ ValuesNext(ValuesScanState *node)
                foreach(lc, exprstatelist)
                {
                        ExprState  *estate = (ExprState *) lfirst(lc);
-                       Form_pg_attribute attr = 
TupleDescAttr(slot->tts_tupleDescriptor,
-                                                                               
                   resind);
+                       TupleAttrAlign attr = 
TupleDescAttrAlign(slot->tts_tupleDescriptor,
+                                                                               
                         resind);
 
                        values[resind] = ExecEvalExpr(estate,
                                                                                
  econtext,
diff --git a/src/backend/utils/adt/expandedrecord.c 
b/src/backend/utils/adt/expandedrecord.c
index e19491ecf7..c17f4580b9 100644
--- a/src/backend/utils/adt/expandedrecord.c
+++ b/src/backend/utils/adt/expandedrecord.c
@@ -546,8 +546,10 @@ expanded_record_set_tuple(ExpandedRecordHeader *erh,
 
                for (i = 0; i < erh->nfields; i++)
                {
+                       TupleAttrAlign align = TupleDescAttrAlign(tupdesc, i);
+
                        if (!erh->dnulls[i] &&
-                               !(TupleDescAttr(tupdesc, i)->attbyval))
+                               !(align->attlen >= 0 && align->attbyval))
                        {
                                char       *oldValue = (char *) 
DatumGetPointer(erh->dvalues[i]);
 
@@ -699,7 +701,7 @@ ER_get_flat_size(ExpandedObjectHeader *eohptr)
        {
                for (i = 0; i < erh->nfields; i++)
                {
-                       Form_pg_attribute attr = TupleDescAttr(tupdesc, i);
+                       TupleAttrAlign attr = TupleDescAttrAlign(tupdesc, i);
 
                        if (!erh->dnulls[i] &&
                                !attr->attbyval && attr->attlen == -1 &&
@@ -1115,7 +1117,7 @@ expanded_record_set_field_internal(ExpandedRecordHeader 
*erh, int fnumber,
                                                                   bool 
check_constraints)
 {
        TupleDesc       tupdesc;
-       Form_pg_attribute attr;
+       TupleAttrAlign attr;
        Datum      *dvalues;
        bool       *dnulls;
        char       *oldValue;
@@ -1146,8 +1148,8 @@ expanded_record_set_field_internal(ExpandedRecordHeader 
*erh, int fnumber,
         * Copy new field value into record's context, and deal with detoasting,
         * if needed.
         */
-       attr = TupleDescAttr(tupdesc, fnumber - 1);
-       if (!isnull && !attr->attbyval)
+       attr = TupleDescAttrAlign(tupdesc, fnumber - 1);
+       if (!isnull && !(attr->attlen >= 0 && attr->attbyval))
        {
                MemoryContext oldcxt;
 
@@ -1541,7 +1543,7 @@ check_domain_for_new_field(ExpandedRecordHeader *erh, int 
fnumber,
         */
        if (!isnull)
        {
-               Form_pg_attribute attr = TupleDescAttr(erh->er_tupdesc, fnumber 
- 1);
+               TupleAttrAlign attr = TupleDescAttrAlign(erh->er_tupdesc, 
fnumber - 1);
 
                if (!attr->attbyval && attr->attlen == -1 &&
                        VARATT_IS_EXTERNAL(DatumGetPointer(newValue)))
diff --git a/src/backend/utils/adt/ri_triggers.c 
b/src/backend/utils/adt/ri_triggers.c
index 96269fc2ad..a18d662262 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -2762,7 +2762,7 @@ ri_KeysEqual(Relation rel, TupleTableSlot *oldslot, 
TupleTableSlot *newslot,
                         * difference for ON UPDATE CASCADE, but for 
consistency we treat
                         * all changes to the PK the same.
                         */
-                       Form_pg_attribute att = 
TupleDescAttr(oldslot->tts_tupleDescriptor, attnums[i] - 1);
+                       TupleAttrAlign att = 
TupleDescAttrAlign(oldslot->tts_tupleDescriptor, attnums[i] - 1);
 
                        if (!datum_image_eq(oldvalue, newvalue, att->attbyval, 
att->attlen))
                                return false;
diff --git a/src/backend/utils/cache/catcache.c 
b/src/backend/utils/cache/catcache.c
index 4fbdc62d8c..add24fa06c 100644
--- a/src/backend/utils/cache/catcache.c
+++ b/src/backend/utils/cache/catcache.c
@@ -1917,14 +1917,14 @@ CatCacheFreeKeys(TupleDesc tupdesc, int nkeys, int 
*attnos, Datum *keys)
        for (i = 0; i < nkeys; i++)
        {
                int                     attnum = attnos[i];
-               Form_pg_attribute att;
+               TupleAttrAlign att;
 
                /* system attribute are not supported in caches */
                Assert(attnum > 0);
 
-               att = TupleDescAttr(tupdesc, attnum - 1);
+               att = TupleDescAttrAlign(tupdesc, attnum - 1);
 
-               if (!att->attbyval)
+               if (att->attlen < 0 || !att->attbyval)
                        pfree(DatumGetPointer(keys[i]));
        }
 }
diff --git a/src/backend/utils/cache/relcache.c 
b/src/backend/utils/cache/relcache.c
index 13d9994af3..070762a027 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -633,6 +633,8 @@ RelationBuildTupleDesc(Relation relation)
        systable_endscan(pg_attribute_scan);
        table_close(pg_attribute_desc, AccessShareLock);
 
+       TupleDescInitAttrAligns(relation->rd_att);
+
        if (need != 0)
                elog(ERROR, "pg_attribute catalog is missing %d attribute(s) 
for relation OID %u",
                         need, RelationGetRelid(relation));
@@ -647,7 +649,8 @@ RelationBuildTupleDesc(Relation relation)
                int                     i;
 
                for (i = 0; i < RelationGetNumberOfAttributes(relation); i++)
-                       Assert(TupleDescAttr(relation->rd_att, i)->attcacheoff 
== -1);
+                       Assert(TupleDescAttr(relation->rd_att, i)->attcacheoff 
== -1 &&
+                                          TupleDescAttrAlign(relation->rd_att, 
i)->attcacheoff == -1);
        }
 #endif
 
@@ -657,7 +660,10 @@ RelationBuildTupleDesc(Relation relation)
         * for attnum=1 that used to exist in fastgetattr() and index_getattr().
         */
        if (RelationGetNumberOfAttributes(relation) > 0)
+       {
                TupleDescAttr(relation->rd_att, 0)->attcacheoff = 0;
+               TupleDescAttrAlign(relation->rd_att, 0)->attcacheoff = 0;
+       }
 
        /*
         * Set up constraint/default info
@@ -1904,6 +1910,8 @@ formrdesc(const char *relationName, Oid relationReltype,
        /* initialize first attribute's attcacheoff, cf RelationBuildTupleDesc 
*/
        TupleDescAttr(relation->rd_att, 0)->attcacheoff = 0;
 
+       TupleDescInitAttrAligns(relation->rd_att);
+
        /* mark not-null status */
        if (has_not_null)
        {
@@ -2038,6 +2046,7 @@ RelationIdGetRelation(Oid relationId)
                        Assert(rd->rd_isvalid ||
                                   (rd->rd_isnailed && 
!criticalRelcachesBuilt));
                }
+               Assert(TupleDescAttrAlignsIsConsistent(rd->rd_att));
                return rd;
        }
 
@@ -2047,7 +2056,10 @@ RelationIdGetRelation(Oid relationId)
         */
        rd = RelationBuildDesc(relationId, true);
        if (RelationIsValid(rd))
+       {
                RelationIncrementReferenceCount(rd);
+               Assert(TupleDescAttrAlignsIsConsistent(rd->rd_att));
+       }
        return rd;
 }
 
@@ -4211,6 +4223,8 @@ BuildHardcodedDescriptor(int natts, const 
FormData_pg_attribute *attrs)
        /* initialize first attribute's attcacheoff, cf RelationBuildTupleDesc 
*/
        TupleDescAttr(result, 0)->attcacheoff = 0;
 
+       TupleDescInitAttrAligns(result);
+
        /* Note: we don't bother to set up a TupleConstr entry */
 
        MemoryContextSwitchTo(oldcxt);
@@ -6086,6 +6100,8 @@ load_relcache_init_file(bool shared)
                rel->rd_amcache = NULL;
                MemSet(&rel->pgstat_info, 0, sizeof(rel->pgstat_info));
 
+               TupleDescInitAttrAligns(rel->rd_att);
+
                /*
                 * Recompute lock and physical addressing info.  This is needed 
in
                 * case the pg_internal.init file was copied from some other 
database
diff --git a/src/include/access/htup_details.h 
b/src/include/access/htup_details.h
index 960772f76b..92c1ac72e4 100644
--- a/src/include/access/htup_details.h
+++ b/src/include/access/htup_details.h
@@ -708,33 +708,33 @@ struct MinimalTupleData
 
 #if !defined(DISABLE_COMPLEX_MACRO)
 
-#define fastgetattr(tup, attnum, tupleDesc, isnull)                            
        \
-(                                                                              
                                                        \
-       AssertMacro((attnum) > 0),                                              
                                \
-       (*(isnull) = false),                                                    
                                \
-       HeapTupleNoNulls(tup) ?                                                 
                                \
-       (                                                                       
                                                        \
-               TupleDescAttr((tupleDesc), (attnum)-1)->attcacheoff >= 0 ?      
\
-               (                                                               
                                                        \
-                       fetchatt(TupleDescAttr((tupleDesc), (attnum)-1),        
        \
-                               (char *) (tup)->t_data + (tup)->t_data->t_hoff 
+        \
-                               TupleDescAttr((tupleDesc), 
(attnum)-1)->attcacheoff)\
-               )                                                               
                                                        \
-               :                                                               
                                                        \
-                       nocachegetattr((tup), (attnum), (tupleDesc))            
        \
-       )                                                                       
                                                        \
-       :                                                                       
                                                        \
-       (                                                                       
                                                        \
-               att_isnull((attnum)-1, (tup)->t_data->t_bits) ?                 
        \
-               (                                                               
                                                        \
-                       (*(isnull) = true),                                     
                                        \
-                       (Datum)NULL                                             
                                                \
-               )                                                               
                                                        \
-               :                                                               
                                                        \
-               (                                                               
                                                        \
-                       nocachegetattr((tup), (attnum), (tupleDesc))            
        \
-               )                                                               
                                                        \
-       )                                                                       
                                                        \
+#define fastgetattr(tup, attnum, tupleDesc, isnull)                            
                        \
+(                                                                              
                                                                        \
+       AssertMacro((attnum) > 0),                                              
                                                \
+       (*(isnull) = false),                                                    
                                                \
+       HeapTupleNoNulls(tup) ?                                                 
                                                \
+       (                                                                       
                                                                        \
+               TupleDescAttrAlign((tupleDesc), (attnum)-1)->attcacheoff >= 0 ? 
        \
+               (                                                               
                                                                        \
+                       fetchatt(TupleDescAttrAlign((tupleDesc), (attnum)-1),   
                \
+                               (char *) (tup)->t_data + (tup)->t_data->t_hoff 
+                        \
+                               TupleDescAttrAlign((tupleDesc), 
(attnum)-1)->attcacheoff)       \
+               )                                                               
                                                                        \
+               :                                                               
                                                                        \
+                       nocachegetattr((tup), (attnum), (tupleDesc))            
                        \
+       )                                                                       
                                                                        \
+       :                                                                       
                                                                        \
+       (                                                                       
                                                                        \
+               att_isnull((attnum)-1, (tup)->t_data->t_bits) ?                 
                        \
+               (                                                               
                                                                        \
+                       (*(isnull) = true),                                     
                                                        \
+                       (Datum)NULL                                             
                                                                \
+               )                                                               
                                                                        \
+               :                                                               
                                                                        \
+               (                                                               
                                                                        \
+                       nocachegetattr((tup), (attnum), (tupleDesc))            
                        \
+               )                                                               
                                                                        \
+       )                                                                       
                                                                        \
 )
 #else                                                  /* 
defined(DISABLE_COMPLEX_MACRO) */
 
diff --git a/src/include/access/itup.h b/src/include/access/itup.h
index 1917375cde..7b739dcd4f 100644
--- a/src/include/access/itup.h
+++ b/src/include/access/itup.h
@@ -99,15 +99,16 @@ typedef IndexAttributeBitMapData * IndexAttributeBitMap;
  */
 #define index_getattr(tup, attnum, tupleDesc, isnull) \
 ( \
-       AssertMacro(PointerIsValid(isnull) && (attnum) > 0), \
+       AssertMacro(PointerIsValid(isnull) && (attnum) > 0 && \
+               TupleDescAttrAlignsIsConsistent(tupleDesc)), \
        *(isnull) = false, \
        !IndexTupleHasNulls(tup) ? \
        ( \
-               TupleDescAttr((tupleDesc), (attnum)-1)->attcacheoff >= 0 ? \
+               TupleDescAttrAlign((tupleDesc), (attnum)-1)->attcacheoff >= 0 ? 
\
                ( \
-                       fetchatt(TupleDescAttr((tupleDesc), (attnum)-1), \
+                       fetchatt(TupleDescAttrAlign((tupleDesc), (attnum)-1), \
                        (char *) (tup) + IndexInfoFindDataOffset((tup)->t_info) 
\
-                       + TupleDescAttr((tupleDesc), (attnum)-1)->attcacheoff) \
+                       + TupleDescAttrAlign((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 f45d47aab7..0e0cdc16cf 100644
--- a/src/include/access/tupdesc.h
+++ b/src/include/access/tupdesc.h
@@ -45,6 +45,19 @@ typedef struct TupleConstr
        bool            has_generated_stored;
 } TupleConstr;
 
+typedef struct TupleAttrAlignData
+{
+       int32           attcacheoff;
+       int16           attlen;
+       char            attalign;
+       union {
+               char    attstorage;
+               bool    attbyval;
+       };
+} TupleAttrAlignData;
+
+typedef TupleAttrAlignData *TupleAttrAlign;
+
 /*
  * This struct is passed around within the backend to describe the structure
  * of tuples.  For tuples coming from on-disk relations, the information is
@@ -82,14 +95,18 @@ typedef struct TupleDescData
        Oid                     tdtypeid;               /* composite type ID 
for tuple type */
        int32           tdtypmod;               /* typmod for tuple type */
        int                     tdrefcount;             /* reference count, or 
-1 if not counting */
+       int                     attroff;        /* offset to a co-allocated 
array of FormData_pg_attribute */
        TupleConstr *constr;            /* constraints, or NULL if none */
-       /* attrs[N] is the description of Attribute Number N+1 */
-       FormData_pg_attribute attrs[FLEXIBLE_ARRAY_MEMBER];
+       /* attrs[N] is the alignment data of Attribute Number N+1 */
+       TupleAttrAlignData attrs[FLEXIBLE_ARRAY_MEMBER];
 }                      TupleDescData;
 typedef struct TupleDescData *TupleDesc;
 
 /* Accessor for the i'th attribute of tupdesc. */
-#define TupleDescAttr(tupdesc, i) (&(tupdesc)->attrs[(i)])
+//#define TupleDescAttr(tupdesc, i) (&(tupdesc)->attrs[(i)])
+#define TupleDescAttr(tupdesc, i) ((FormData_pg_attribute *) ((char 
*)(tupdesc) + (tupdesc)->attroff + (i) * sizeof(FormData_pg_attribute)))
+/* Accessor for the i'th attribute of attralign. */
+#define TupleDescAttrAlign(tupdesc, i) (&(tupdesc)->attrs[(i)])
 
 extern TupleDesc CreateTemplateTupleDesc(int natts);
 
@@ -100,8 +117,9 @@ extern TupleDesc CreateTupleDescCopy(TupleDesc tupdesc);
 extern TupleDesc CreateTupleDescCopyConstr(TupleDesc tupdesc);
 
 #define TupleDescSize(src) \
-       (offsetof(struct TupleDescData, attrs) + \
-        (src)->natts * sizeof(FormData_pg_attribute))
+       (MAXALIGN(offsetof(struct TupleDescData, attrs) + \
+        (src)->natts * sizeof(TupleAttrAlignData)) + \
+        MAXALIGN((src)->natts * sizeof(FormData_pg_attribute)))
 
 extern void TupleDescCopy(TupleDesc dst, TupleDesc src);
 
@@ -125,6 +143,17 @@ extern void DecrTupleDescRefCount(TupleDesc tupdesc);
                        DecrTupleDescRefCount(tupdesc); \
        } while (0)
 
+#define CopyAttrToAlign(att, align) \
+       do { \
+               (align)->attcacheoff = (att)->attcacheoff; \
+               (align)->attlen = (att)->attlen; \
+               (align)->attalign = (att)->attalign; \
+               if ((att)->attlen < 0) \
+                       (align)->attstorage = (att)->attstorage; \
+               else \
+                       (align)->attbyval = (att)->attbyval; \
+       } while (0)
+
 extern bool equalTupleDescs(TupleDesc tupdesc1, TupleDesc tupdesc2);
 
 extern uint32 hashTupleDesc(TupleDesc tupdesc);
@@ -147,6 +176,9 @@ extern void TupleDescInitEntryCollation(TupleDesc desc,
                                                                                
AttrNumber attributeNumber,
                                                                                
Oid collationid);
 
+extern void TupleDescInitAttrAligns(TupleDesc desc);
+extern bool TupleDescAttrAlignsIsConsistent(TupleDesc desc);
+
 extern TupleDesc BuildDescForRelation(List *schema);
 
 extern TupleDesc BuildDescFromLists(List *names, List *types, List *typmods, 
List *collations);
diff --git a/src/include/access/tupmacs.h b/src/include/access/tupmacs.h
index 65ac1ef3fc..0451dd6bb8 100644
--- a/src/include/access/tupmacs.h
+++ b/src/include/access/tupmacs.h
@@ -47,7 +47,7 @@
 
 #define fetch_att(T,attbyval,attlen) \
 ( \
-       (attbyval) ? \
+       (attbyval && attlen >= 0) ? \
        ( \
                (attlen) == (int) sizeof(Datum) ? \
                        *((Datum *)(T)) \
diff --git a/src/include/catalog/pg_attribute.h 
b/src/include/catalog/pg_attribute.h
index 5c1ec9313e..0bfb573c41 100644
--- a/src/include/catalog/pg_attribute.h
+++ b/src/include/catalog/pg_attribute.h
@@ -94,6 +94,11 @@ CATALOG(pg_attribute,1249,AttributeRelationId) BKI_BOOTSTRAP 
BKI_ROWTYPE_OID(75,
         * no cached value.  But when we copy these tuples into a tuple
         * descriptor, we may then update attcacheoff in the copies. This speeds
         * up the attribute walking process.
+        *
+        * Note: Although the maximum offset encountered in stored tuples is
+        * limited to the max BLCKSZ (2**15), FormData_pg_attribute is used for
+        * all internal tuples as well, so attcacheoff may be larger for those
+        * tuples, and it is therefore not safe to use int16.
         */
        int32           attcacheoff BKI_DEFAULT(-1);
 
-- 
2.20.1

Reply via email to