Fix tuple deforming with virtual generated columns

TupleDescFinalize() failed to take into account virtual generated
columns, which are always stored as NULL in tuples.  TupleDescFinalize()
didn't check for this, and that could result in attcacheoff being set for
and beyond virtual generated columns.  Also, the TupleDesc's
firstNonGuaranteedAttr could also be set incorrectly, which could result
in the tuple deformation function deforming without checking for NULLs,
and deforming using incorrectly cached offsets.

This could result in tuples being deformed incorrectly, which could
result in incorrect results, ERRORs or possibly a crash.

This has been broken since c456e39113.

Author: Chao Li <li.evan.chao@gmail.com>
Reported-by: Chao Li <li.evan.chao@gmail.com>
Reviewed-by: ChangAo Chen <cca5507@qq.com>
Reviewed-by: David Rowley <dgrowleyml@gmail.com>
Discussion: https://postgr.es/m/A4BC563C-0CA3-4EF3-952A-EA41F9E5BF1E%40gmail.com
This commit is contained in:
David Rowley 2026-06-06 16:45:29 +12:00
parent 193a4ded94
commit 89eafad297
2 changed files with 18 additions and 2 deletions

View file

@ -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);

View file

@ -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