From f54f13e29b56fde249c96ffeb3d5044239f6927c Mon Sep 17 00:00:00 2001
From: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Date: Tue, 1 Sep 2026 03:20:50 +0000
Subject: [PATCH v4] Restrict REPACK (CONCURRENTLY) on user catalog tables.

REPACK (CONCURRENTLY) is not an MVCC-safe operation; it doesn't
preserve the visibility information, which logical decoding needs
because it reads user catalog tables under a historic snapshot.

Disallow REPACK (CONCURRENTLY) on user catalog tables. Removing this
check requires making it MVCC-safe and logical rewrite mappings.

Backpatch to v19, where REPACK (CONCURRENTLY) was introduced.

Reported-by: Nathan Bossart <nathandbossart@gmail.com>
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Reviewed-by: Antonin Houska <ah@cybertec.at>
Reviewed-by: Masahiko Sawada <sawada.mshk@gmail.com>
Discussion: https://postgr.es/m/apBIFWzHYOaG0auN%40nathan
Backpatch-through: 19
---
 contrib/test_decoding/expected/repack.out |  6 ++++++
 contrib/test_decoding/sql/repack.sql      |  5 +++++
 doc/src/sgml/ref/repack.sgml              |  8 ++++++++
 src/backend/commands/repack.c             | 14 ++++++++++++++
 4 files changed, 33 insertions(+)

diff --git a/contrib/test_decoding/expected/repack.out b/contrib/test_decoding/expected/repack.out
index 5ddc63238c5..6ed0af0f8ad 100644
--- a/contrib/test_decoding/expected/repack.out
+++ b/contrib/test_decoding/expected/repack.out
@@ -61,6 +61,12 @@ HINT:  Consider running the command on individual partitions.
 REPACK (CONCURRENTLY) pg_class;
 ERROR:  cannot execute REPACK (CONCURRENTLY) on relation "pg_class"
 HINT:  REPACK (CONCURRENTLY) is not supported for catalog relations.
+-- Doesn't support tables used as catalog tables
+CREATE TABLE repack_conc_user_catalog (i int) WITH (user_catalog_table = true);
+REPACK (CONCURRENTLY) repack_conc_user_catalog;
+ERROR:  cannot execute REPACK (CONCURRENTLY) on relation "repack_conc_user_catalog"
+HINT:  REPACK (CONCURRENTLY) is not supported for tables used as catalog tables.
+DROP TABLE repack_conc_user_catalog;
 -- Doesn't support TOAST tables directly
 CREATE TABLE repack_conc_toast (t text);
 SELECT reltoastrelid::regclass AS toast_rel
diff --git a/contrib/test_decoding/sql/repack.sql b/contrib/test_decoding/sql/repack.sql
index f461f5479f4..eb891009baf 100644
--- a/contrib/test_decoding/sql/repack.sql
+++ b/contrib/test_decoding/sql/repack.sql
@@ -42,6 +42,11 @@ REPACK (CONCURRENTLY) clstrpart;
 -- Disallowed in catalogs
 REPACK (CONCURRENTLY) pg_class;
 
+-- Doesn't support tables used as catalog tables
+CREATE TABLE repack_conc_user_catalog (i int) WITH (user_catalog_table = true);
+REPACK (CONCURRENTLY) repack_conc_user_catalog;
+DROP TABLE repack_conc_user_catalog;
+
 -- Doesn't support TOAST tables directly
 CREATE TABLE repack_conc_toast (t text);
 SELECT reltoastrelid::regclass AS toast_rel
diff --git a/doc/src/sgml/ref/repack.sgml b/doc/src/sgml/ref/repack.sgml
index 0cb72b6b289..e2d5a34ba8b 100644
--- a/doc/src/sgml/ref/repack.sgml
+++ b/doc/src/sgml/ref/repack.sgml
@@ -285,6 +285,14 @@ REPACK [ ( <replaceable class="parameter">option</replaceable> [, ...] ) ] USING
         </para>
        </listitem>
 
+       <listitem>
+        <para>
+          The table is declared as a catalog table using the
+          <link linkend="reloption-user-catalog-table"><literal>user_catalog_table</literal></link>
+          storage parameter.
+        </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..b42fda303a2 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -886,6 +886,20 @@ check_concurrent_repack_requirements(Relation rel, Oid *ident_idx_p)
 				errhint("%s is not supported for catalog relations.",
 						"REPACK (CONCURRENTLY)"));
 
+	/*
+	 * REPACK (CONCURRENTLY) is not MVCC-safe; it doesn't preserve visibility
+	 * information, which logical decoding needs because it reads user catalog
+	 * tables under a historic snapshot. Removing this check requires making
+	 * it MVCC-safe and logical rewrite mappings.
+	 */
+	if (RelationIsUsedAsCatalogTable(rel))
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("cannot execute %s on relation \"%s\"",
+					   "REPACK (CONCURRENTLY)", RelationGetRelationName(rel)),
+				errhint("%s is not supported for tables used as catalog tables.",
+						"REPACK (CONCURRENTLY)"));
+
 	/*
 	 * reorderbuffer.c does not seem to handle processing of TOAST relation
 	 * alone.
-- 
2.55.0

