From bd770413e8dbfcd626ce624635cfa8b337def882 Mon Sep 17 00:00:00 2001
From: shihao zhong <zhong950419@gmail.com>
Date: Tue, 6 Oct 2026 21:44:11 -0400
Subject: [PATCH v2 1/2] Clear out dropped-column values from inserts replayed
 by REPACK

e5d25959cf8 cleared values of dropped columns only when replaying an
UPDATE.  A concurrent INSERT can carry one too, for example when a
trigger returns a copy of an existing row.  REPACK (CONCURRENTLY) then
kept it, or failed with "row is too big" if the new heap has no TOAST
table.  Clear them when replaying an INSERT as well.

Discussion: https://postgr.es/m/CAJgoLkK2UBzB1J9buCsSUbjf7bOqz-o_0=CeiTruU789BhBw-Q@mail.gmail.com
---
 src/backend/commands/repack.c | 18 ++++++++++++++++++
 1 file changed, 18 insertions(+)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 899005609c2..c16ed5f4714 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2789,6 +2789,24 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 
 		if (kind == CHANGE_INSERT)
 		{
+			TupleDesc	desc = spilled_tuple->tts_tupleDescriptor;
+
+			/*
+			 * Dropped columns can still have values in the tuple.  Mark them
+			 * as null, like prepare_concurrent_update() does: the new heap
+			 * might have no TOAST table to store them in.
+			 */
+			for (int i = 0; i < desc->natts; i++)
+			{
+				CompactAttribute *attr = TupleDescCompactAttr(desc, i);
+
+				if (attr->attisdropped && !slot_attisnull(spilled_tuple, i + 1))
+				{
+					slot_getsomeattrs(spilled_tuple, i + 1);
+					spilled_tuple->tts_isnull[i] = true;
+				}
+			}
+
 			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
-- 
2.37.1 (Apple Git-137.1)

