From 36f1fd2c8f31899d08e0b291d49030c200ad7e32 Mon Sep 17 00:00:00 2001 From: "Chao Li (Evan)" Date: Sun, 26 Apr 2026 15:31:24 +0800 Subject: [PATCH v4] Reject deferrable primary key fallback in REPACK CONCURRENTLY REPACK CONCURRENTLY uses logical decoding to collect concurrent changes and then replays them on the new heap. To locate rows for UPDATE and DELETE replay, it requires an identity index. When RelationGetReplicaIndex() returned InvalidOid, the code fell back to rel->rd_pkindex if a primary key existed. That is not safe for deferrable primary keys. Such indexes are not considered replica identity indexes by WAL generation, so logical decoding may not provide the old tuple needed by the repack output plugin. This can make replay fail later with errors such as "incomplete delete info" from the decoding worker. This change switches to use GetRelationIdentityOrPK() that excludes deferrable primary key. Author: Chao Li Reviewed-by: Zhijie Hou Reviewed-by: Antonin Houska Discussion: https://postgr.es/m/10DD5E13-B45D-44F1-BE08-C63E00ABCAC0@gmail.com --- src/backend/commands/repack.c | 15 ++++---- src/test/regress/expected/cluster.out | 50 +++++++++++++++++++++++++-- src/test/regress/sql/cluster.sql | 44 +++++++++++++++++++++-- 3 files changed, 97 insertions(+), 12 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index bafdca80810..ce5b99c38b1 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -62,6 +62,7 @@ #include "miscadmin.h" #include "optimizer/optimizer.h" #include "pgstat.h" +#include "replication/logicalrelation.h" #include "storage/bufmgr.h" #include "storage/lmgr.h" #include "storage/predicate.h" @@ -919,14 +920,12 @@ check_concurrent_repack_requirements(Relation rel, Oid *ident_idx_p) /* * Obtain the replica identity index -- either one that has been set - * explicitly, or the primary key. If none of these cases apply, the - * table cannot be repacked concurrently. It might be possible to have - * repack work with a FULL replica identity; however that requires more - * work and is not implemented yet. - */ - ident_idx = RelationGetReplicaIndex(rel); - if (!OidIsValid(ident_idx) && OidIsValid(rel->rd_pkindex)) - ident_idx = rel->rd_pkindex; + * explicitly, or a non-deferrable primary key. If none of these cases + * apply, the table cannot be repacked concurrently. It might be possible + * to have repack work with a FULL replica identity; however that requires + * more work and is not implemented yet. + */ + ident_idx = GetRelationIdentityOrPK(rel); if (!OidIsValid(ident_idx)) ereport(ERROR, errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), diff --git a/src/test/regress/expected/cluster.out b/src/test/regress/expected/cluster.out index 96089bb0fa2..031664b95f6 100644 --- a/src/test/regress/expected/cluster.out +++ b/src/test/regress/expected/cluster.out @@ -796,14 +796,60 @@ ORDER BY o.relname; clstr_3 (2 rows) --- concurrently disallowed in catalogs +-- +-- Check concurrent mode requirements +-- +-- Disallowed in catalogs REPACK (CONCURRENTLY) pg_class; ERROR: cannot repack relation "pg_class" HINT: REPACK CONCURRENTLY is not supported for catalog relations. --- CONCURRENTLY doesn't like partitioned tables +-- Doesn't like partitioned tables REPACK (CONCURRENTLY) clstrpart; ERROR: REPACK (CONCURRENTLY) is not supported for partitioned tables HINT: Consider running the command on individual partitions. +-- Doesn't support catalog tables +REPACK (CONCURRENTLY) pg_class; +ERROR: cannot repack relation "pg_class" +HINT: REPACK CONCURRENTLY is not supported for catalog relations. +-- Only support permanent tables, temp and unlogged tables are not supported +CREATE TEMP TABLE repack_conc_temp (i int PRIMARY KEY); +REPACK (CONCURRENTLY) repack_conc_temp; +ERROR: cannot repack relation "repack_conc_temp" +HINT: REPACK CONCURRENTLY is only allowed for permanent relations. +DROP TABLE repack_conc_temp; +CREATE UNLOGGED TABLE repack_conc_unlogged (i int PRIMARY KEY); +REPACK (CONCURRENTLY) repack_conc_unlogged; +ERROR: cannot repack relation "repack_conc_unlogged" +HINT: REPACK CONCURRENTLY is only allowed for permanent relations. +DROP TABLE repack_conc_unlogged; +-- Doesn't support tables without a primary key or replica identity index +CREATE TABLE repack_conc_noident (i int); +REPACK (CONCURRENTLY) repack_conc_noident; +ERROR: cannot process relation "repack_conc_noident" +HINT: Relation "repack_conc_noident" has no identity index. +DROP TABLE repack_conc_noident; +-- Doesn't support TOAST tables directly +CREATE TABLE repack_conc_toast (t text); +SELECT reltoastrelid::regclass AS toast_rel +FROM pg_class WHERE oid = 'repack_conc_toast'::regclass \gset +\set VERBOSITY sqlstate +REPACK (CONCURRENTLY) :toast_rel; +ERROR: 0A000 +\set VERBOSITY default +DROP TABLE repack_conc_toast; +-- Doesn't support tables with REPLICA IDENTITY NOTHING, even if they have a primary key +CREATE TABLE repack_conc_nothing (i int PRIMARY KEY); +ALTER TABLE repack_conc_nothing REPLICA IDENTITY NOTHING; +REPACK (CONCURRENTLY) repack_conc_nothing; +ERROR: cannot repack relation "repack_conc_nothing" +HINT: Relation "repack_conc_nothing" has insufficient replication identity. +DROP TABLE repack_conc_nothing; +-- Doesn't support tables with deferrable primary keys +CREATE TABLE repack_conc_deferrable (i int PRIMARY KEY DEFERRABLE); +REPACK (CONCURRENTLY) repack_conc_deferrable; +ERROR: cannot process relation "repack_conc_deferrable" +HINT: Relation "repack_conc_deferrable" has no identity index. +DROP TABLE repack_conc_deferrable; -- clean up DROP TABLE clustertest; DROP TABLE clstr_1; diff --git a/src/test/regress/sql/cluster.sql b/src/test/regress/sql/cluster.sql index 6b3219bab94..640b3691f44 100644 --- a/src/test/regress/sql/cluster.sql +++ b/src/test/regress/sql/cluster.sql @@ -383,12 +383,52 @@ JOIN relnodes_new n ON o.relname = n.relname WHERE o.relfilenode <> n.relfilenode ORDER BY o.relname; --- concurrently disallowed in catalogs +-- +-- Check concurrent mode requirements +-- + +-- Disallowed in catalogs REPACK (CONCURRENTLY) pg_class; --- CONCURRENTLY doesn't like partitioned tables +-- Doesn't like partitioned tables REPACK (CONCURRENTLY) clstrpart; +-- Doesn't support catalog tables +REPACK (CONCURRENTLY) pg_class; + +-- Only support permanent tables, temp and unlogged tables are not supported +CREATE TEMP TABLE repack_conc_temp (i int PRIMARY KEY); +REPACK (CONCURRENTLY) repack_conc_temp; +DROP TABLE repack_conc_temp; +CREATE UNLOGGED TABLE repack_conc_unlogged (i int PRIMARY KEY); +REPACK (CONCURRENTLY) repack_conc_unlogged; +DROP TABLE repack_conc_unlogged; + +-- Doesn't support tables without a primary key or replica identity index +CREATE TABLE repack_conc_noident (i int); +REPACK (CONCURRENTLY) repack_conc_noident; +DROP TABLE repack_conc_noident; + +-- Doesn't support TOAST tables directly +CREATE TABLE repack_conc_toast (t text); +SELECT reltoastrelid::regclass AS toast_rel +FROM pg_class WHERE oid = 'repack_conc_toast'::regclass \gset +\set VERBOSITY sqlstate +REPACK (CONCURRENTLY) :toast_rel; +\set VERBOSITY default +DROP TABLE repack_conc_toast; + +-- Doesn't support tables with REPLICA IDENTITY NOTHING, even if they have a primary key +CREATE TABLE repack_conc_nothing (i int PRIMARY KEY); +ALTER TABLE repack_conc_nothing REPLICA IDENTITY NOTHING; +REPACK (CONCURRENTLY) repack_conc_nothing; +DROP TABLE repack_conc_nothing; + +-- Doesn't support tables with deferrable primary keys +CREATE TABLE repack_conc_deferrable (i int PRIMARY KEY DEFERRABLE); +REPACK (CONCURRENTLY) repack_conc_deferrable; +DROP TABLE repack_conc_deferrable; + -- clean up DROP TABLE clustertest; DROP TABLE clstr_1; -- 2.50.1 (Apple Git-155)