From 9b94ac46635753e0dd26787416865bb00aa397a4 Mon Sep 17 00:00:00 2001
From: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Date: Tue, 1 Sep 2026 03:29:27 +0000
Subject: [PATCH v4] Restrict REPACK (CONCURRENTLY) to the heap access method.

REPACK (CONCURRENTLY) didn't check the table AM of the table being
repacked, so if the table AM doesn't support logical decoding, the
concurrent changes are never decoded and are silently lost from the
rewritten table.

Fix by erroring out for tables that use a non-heap access method.

Reported-by: Nathan Bossart <nathandbossart@gmail.com>
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Reviewed-by: Nathan Bossart <nathandbossart@gmail.com>
Reviewed-by: Masahiko Sawada <sawada.mshk@gmail.com>
Discussion: https://postgr.es/m/apBkixO4xAGFoiT3@nathan
Backpatch-through: 19
---
 doc/src/sgml/ref/repack.sgml  |  6 ++++++
 src/backend/commands/repack.c | 15 +++++++++++++++
 2 files changed, 21 insertions(+)

diff --git a/doc/src/sgml/ref/repack.sgml b/doc/src/sgml/ref/repack.sgml
index 0cb72b6b289..0496cd24af1 100644
--- a/doc/src/sgml/ref/repack.sgml
+++ b/doc/src/sgml/ref/repack.sgml
@@ -285,6 +285,12 @@ REPACK [ ( <replaceable class="parameter">option</replaceable> [, ...] ) ] USING
         </para>
        </listitem>
 
+       <listitem>
+        <para>
+          The table uses an access method other than <literal>heap</literal>.
+        </para>
+       </listitem>
+
        <listitem>
         <para>
          <command>REPACK</command> is executed inside a transaction block.
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 477c86b2ba6..91e46532132 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -877,6 +877,21 @@ check_concurrent_repack_requirements(Relation rel, Oid *ident_idx_p)
 				errdetail("%s requires \"wal_level\" to be set to \"replica\" or higher.",
 						  "REPACK (CONCURRENTLY)"));
 
+	/*
+	 * A table AM that doesn't support logical decoding would cause REPACK
+	 * (CONCURRENTLY) to silently lose the changes made during the rewrite.
+	 * Nothing in TableAmRoutine tells us whether it does, so for now restrict
+	 * to heap.  Check the routine rather than the AM OID, so that an AM
+	 * reusing the heap handler still works.
+	 */
+	if (rel->rd_tableam != GetHeapamTableAmRoutine())
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("cannot execute %s on relation \"%s\"",
+					   "REPACK (CONCURRENTLY)", RelationGetRelationName(rel)),
+				errhint("%s is only supported for the \"heap\" access method.",
+						"REPACK (CONCURRENTLY)"));
+
 	/* Data changes in system relations are not logically decoded. */
 	if (IsCatalogRelation(rel))
 		ereport(ERROR,
-- 
2.55.0

