diff --git a/src/backend/access/common/tupdesc.c b/src/backend/access/common/tupdesc.c index 196472c05d0..36026d3ec3f 100644 --- a/src/backend/access/common/tupdesc.c +++ b/src/backend/access/common/tupdesc.c @@ -517,6 +517,7 @@ TupleDescFinalize(TupleDesc tupdesc) for (int i = 0; i < tupdesc->natts; i++) { CompactAttribute *cattr = TupleDescCompactAttr(tupdesc, i); + Form_pg_attribute attr = TupleDescAttr(tupdesc, i); /* * Find the highest attnum which is guaranteed to exist in all tuples @@ -525,10 +526,18 @@ TupleDescFinalize(TupleDesc tupdesc) */ if (firstNonGuaranteedAttr == tupdesc->natts && (cattr->attnullability != ATTNULLABLE_VALID || !cattr->attbyval || - cattr->atthasmissing || cattr->attisdropped || cattr->attlen <= 0)) + cattr->atthasmissing || cattr->attisdropped || + cattr->attlen <= 0 || + attr->attgenerated == ATTRIBUTE_GENERATED_VIRTUAL)) firstNonGuaranteedAttr = i; - if (cattr->attlen <= 0) + /* + * Don't cache offsets beyond fixed-width attributes. Virtual + * generated attributes are stored as NULLs in the tuple, so we don't + * cache offsets beyond these. + */ + if (cattr->attlen <= 0 || + attr->attgenerated == ATTRIBUTE_GENERATED_VIRTUAL) break; off = att_nominal_alignby(off, cattr->attalignby); diff --git a/src/backend/executor/execTuples.c b/src/backend/executor/execTuples.c index b0a0028b165..7f4ebf95432 100644 --- a/src/backend/executor/execTuples.c +++ b/src/backend/executor/execTuples.c @@ -1074,6 +1074,13 @@ slot_deform_heap_tuple(TupleTableSlot *slot, HeapTuple tuple, uint32 *offp, { /* Otherwise all required columns are guaranteed to exist */ firstNullAttr = natts; + + /* + * Check TupleDescFinalize() didn't get confused when setting + * firstNonGuaranteedAttr. There should never be a NULL in a + * guaranteed column. + */ + Assert(first_null_attr(tup->t_bits, natts) >= firstNullAttr); } } else