From d260d7d003fa241cc3b52769d79187f30e98febd Mon Sep 17 00:00:00 2001 From: David Rowley Date: Thu, 6 Aug 2026 17:42:44 +1200 Subject: [PATCH] Fix unlikely incremental tuple deform bug with missing attrs The code added in c456e3911 added populate_isnull_array() to bulk populate the slot's tts_isnull array 8 elements at a time. When tuples don't have an exact multiple-of-eight number of attributes, this will lead to populating the tts_isnull elements for attributes that don't exist in the tuple. This is ok as the array is large enough. However, if we perform tuple deforming in two passes, and on the first pass deform *some* of the attributes with slot_getmissingattrs() then later when we deform the remaining missing attributes, the subsequent call to populate_isnull_array() would overwrite the tts_isnull values previously set by slot_getmissingattrs(), and since that function only continues where it left off, it wouldn't reapply the previously set values and those would be left as NULLs, as populate_isnull_array() would have set them. Here, we fix by passing the tuple's natts to slot_getmissingattrs() rather than the attnum we're deforming from. This means we apply all the missing attribute values each deform pass, so slightly more work, but deforming several missing values in different deform passes is likely exceedingly rare. Doing that seems much better than adding overhead in the happy path to check for this and skip the subsequent call to populate_isnull_array(). Author: David Rowley Reported-by: Peter Geoghegan Discussion: https://postgr.es/m/CAH2-WznHo4b+6AmAj0GZ0jXqDSK69MfHe8fAQwuY_01y7cVNdw@mail.gmail.com Backpatch-through: 19 --- src/backend/executor/execTuples.c | 12 +++++++++++- src/test/regress/expected/fast_default.out | 15 +++++++++++++++ src/test/regress/sql/fast_default.sql | 10 ++++++++++ 3 files changed, 36 insertions(+), 1 deletion(-) diff --git a/src/backend/executor/execTuples.c b/src/backend/executor/execTuples.c index 97ae019d10a..3ad983c7fa5 100644 --- a/src/backend/executor/execTuples.c +++ b/src/backend/executor/execTuples.c @@ -1254,7 +1254,17 @@ slot_deform_heap_tuple(TupleTableSlot *slot, HeapTuple tuple, uint32 *offp, * to implement a tail-call optimization */ *offp = off; - slot_getmissingattrs(slot, attnum, reqnatts); + + Assert(HeapTupleHeaderGetNatts(tup) <= attnum); + + /* + * Fetch all missing attributes. We pass natts rather than attnum as + * if we're deforming attributes after having already deformed some + * missing attributes, then the call to populate_isnull_array() may + * have overwritten the previous tts_isnull values from what was + * stored in the previous call to slot_getmissingattrs(). + */ + slot_getmissingattrs(slot, HeapTupleHeaderGetNatts(tup), reqnatts); return; } done: diff --git a/src/test/regress/expected/fast_default.out b/src/test/regress/expected/fast_default.out index 49485f5ec6a..20356467655 100644 --- a/src/test/regress/expected/fast_default.out +++ b/src/test/regress/expected/fast_default.out @@ -944,6 +944,21 @@ SELECT * FROM t ORDER BY id; (3 rows) DROP TABLE t; +-- Ensure defaults are correctly applied during tuple deformation for whole +-- row vars when the defaults are deformed incrementally. +CREATE TABLE t_missing_wholerow (a int NOT NULL, b int); +INSERT INTO t_missing_wholerow (a, b) VALUES (1, null); -- with NULL bitmap +INSERT INTO t_missing_wholerow (a, b) VALUES (2, 3); -- without NULL bitmap +ALTER TABLE t_missing_wholerow ADD COLUMN c int NOT NULL DEFAULT 15; +ALTER TABLE t_missing_wholerow ADD COLUMN d bigint NOT NULL DEFAULT 25; +SELECT a, b, c, t FROM t_missing_wholerow t; + a | b | c | t +---+---+----+------------- + 1 | | 15 | (1,,15,25) + 2 | 3 | 15 | (2,3,15,25) +(2 rows) + +DROP TABLE t_missing_wholerow; -- cleanup DROP FOREIGN TABLE ft1; DROP SERVER s0; diff --git a/src/test/regress/sql/fast_default.sql b/src/test/regress/sql/fast_default.sql index ccd138c4efc..36f81a184f0 100644 --- a/src/test/regress/sql/fast_default.sql +++ b/src/test/regress/sql/fast_default.sql @@ -618,6 +618,16 @@ REPACK t; SELECT * FROM t ORDER BY id; DROP TABLE t; +-- Ensure defaults are correctly applied during tuple deformation for whole +-- row vars when the defaults are deformed incrementally. +CREATE TABLE t_missing_wholerow (a int NOT NULL, b int); +INSERT INTO t_missing_wholerow (a, b) VALUES (1, null); -- with NULL bitmap +INSERT INTO t_missing_wholerow (a, b) VALUES (2, 3); -- without NULL bitmap +ALTER TABLE t_missing_wholerow ADD COLUMN c int NOT NULL DEFAULT 15; +ALTER TABLE t_missing_wholerow ADD COLUMN d bigint NOT NULL DEFAULT 25; +SELECT a, b, c, t FROM t_missing_wholerow t; +DROP TABLE t_missing_wholerow; + -- cleanup DROP FOREIGN TABLE ft1; DROP SERVER s0;