From 96179fc6076c7ab0b8d3131e97f7c10f096aef82 Mon Sep 17 00:00:00 2001
From: Antonin Houska <ah@cybertec.at>
Date: Wed, 2 Sep 2026 19:17:53 +0200
Subject: [PATCH 1/2] Reproduce failure when only the TOAST tuple is logically
 decoded.

The bug was introduced by commit 28d534e2ae, in which REPACK (CONCURRENTLY)
suppresses decoding of DML commands in the new heap during repacking. The
problem is that for UPDATE we only disabled decoding of the main tuple, but
not for its TOAST tuple(s).
---
 .../expected/repack_toast_bug.out             | 36 ++++++++++++
 .../specs/repack_toast_bug.spec               | 57 +++++++++++++++++++
 2 files changed, 93 insertions(+)
 create mode 100644 src/test/modules/injection_points/expected/repack_toast_bug.out
 create mode 100644 src/test/modules/injection_points/specs/repack_toast_bug.spec

diff --git a/src/test/modules/injection_points/expected/repack_toast_bug.out b/src/test/modules/injection_points/expected/repack_toast_bug.out
new file mode 100644
index 00000000000..0de24241bfb
--- /dev/null
+++ b/src/test/modules/injection_points/expected/repack_toast_bug.out
@@ -0,0 +1,36 @@
+Parsed test spec with 2 sessions
+
+starting permutation: s1_wait_before_lock s2_changes s2_wakeup_before_lock s1_decode
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_wait_before_lock: 
+	REPACK (CONCURRENTLY) repack_toast;
+ <waiting ...>
+step s2_changes: 
+	UPDATE repack_toast SET t = gen_external() WHERE i=1;
+
+step s2_wakeup_before_lock: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s1_wait_before_lock: <... completed>
+step s1_decode: 
+	SELECT count(*) FROM pg_logical_slot_peek_changes('s', NULL, NULL, 'include-rewrites', '1');
+
+count
+-----
+    9
+(1 row)
+
+pg_drop_replication_slot
+------------------------
+                        
+(1 row)
+
diff --git a/src/test/modules/injection_points/specs/repack_toast_bug.spec b/src/test/modules/injection_points/specs/repack_toast_bug.spec
new file mode 100644
index 00000000000..a09fd8753e0
--- /dev/null
+++ b/src/test/modules/injection_points/specs/repack_toast_bug.spec
@@ -0,0 +1,57 @@
+setup
+{
+	SELECT pg_create_logical_replication_slot('s', 'test_decoding');
+
+	CREATE EXTENSION IF NOT EXISTS injection_points;
+
+	-- Generate a string of random characters that is not likely to be
+	-- compressed, but is big enough to be stored externally.
+	CREATE FUNCTION gen_external()
+	RETURNS text
+	LANGUAGE sql as $$
+		SELECT string_agg(chr(65 + trunc(25 * random())::int), '')
+		FROM generate_series(1, 2048) s(x);
+	$$;
+
+	CREATE TABLE repack_toast(i int PRIMARY KEY, t text);
+	INSERT INTO repack_toast(i, t) VALUES (1, gen_external());
+}
+
+teardown
+{
+	DROP TABLE repack_toast;
+	SELECT pg_drop_replication_slot('s');
+}
+
+session s1
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+# Perform the initial load and wait for s2 to do some data changes.
+step s1_wait_before_lock
+{
+	REPACK (CONCURRENTLY) repack_toast;
+}
+step s1_decode
+{
+	SELECT count(*) FROM pg_logical_slot_peek_changes('s', NULL, NULL, 'include-rewrites', '1');
+}
+
+session s2
+step s2_changes
+{
+	UPDATE repack_toast SET t = gen_external() WHERE i=1;
+}
+step s2_wakeup_before_lock
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+	s1_wait_before_lock
+	s2_changes
+	s2_wakeup_before_lock
+	s1_decode
+
-- 
2.52.0

