agora inbox for [email protected]  
help / color / mirror / Atom feed
[PATCH 1/2] REPACK: do not require the user to have REPLICATION
58+ messages / 2 participants
[nested] [flat]

* [PATCH 1/2] REPACK: do not require the user to have REPLICATION
@ 2026-04-20 09:38  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 58+ messages in thread

From: Álvaro Herrera @ 2026-04-20 09:38 UTC (permalink / raw)

Although REPACK (CONCURRENTLY) uses replication slots, there is no
concern that the slot will leak data of other users, because the
MAINTAIN privilege on the table is required anyway; requiring
REPLICATION is user-unfriendly without providing any actual protection.

A related aspect is that the REPLICATION attribute is not needed to
prevent REPACK from stealing slots from logical replication, since
commit e76d8c749c31 made REPACK use a separate pool of replication
slots.

Because there are now successful concurrent repack runs in the
regression tests, we're forced to run test_plan_advice under
wal_level=replica.

Author: Antonin Houska <[email protected]>
Reported-by: Justin Pryzby <[email protected]>
Reviewed-by: Chao Li <[email protected]>
Discussion: https://postgr.es/m/aeJHPNmL4vVy3oPw@pryzbyj2023
---
 src/backend/commands/repack_worker.c          |  1 -
 .../test_plan_advice/t/001_replan_regress.pl  |  1 +
 src/test/regress/expected/cluster.out         | 20 +++++++++++++++++--
 src/test/regress/sql/cluster.sql              | 11 ++++++++--
 4 files changed, 28 insertions(+), 5 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index b17edd771e2..e4a4860805b 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -214,7 +214,6 @@ repack_setup_logical_decoding(Oid relid)
 	/*
 	 * Make sure we can use logical decoding.
 	 */
-	CheckSlotPermissions();
 	CheckLogicalDecodingRequirements(true);
 
 	/*
diff --git a/src/test/modules/test_plan_advice/t/001_replan_regress.pl b/src/test/modules/test_plan_advice/t/001_replan_regress.pl
index 38ffa4d11ae..452b179a665 100644
--- a/src/test/modules/test_plan_advice/t/001_replan_regress.pl
+++ b/src/test/modules/test_plan_advice/t/001_replan_regress.pl
@@ -18,6 +18,7 @@ $node->init();
 # Set up our desired configuration.
 $node->append_conf('postgresql.conf', <<EOM);
 shared_preload_libraries='test_plan_advice'
+wal_level=replica
 pg_plan_advice.always_explain_supplied_advice=false
 pg_plan_advice.feedback_warnings=true
 EOM
diff --git a/src/test/regress/expected/cluster.out b/src/test/regress/expected/cluster.out
index 6127b215a86..e17bc91fae1 100644
--- a/src/test/regress/expected/cluster.out
+++ b/src/test/regress/expected/cluster.out
@@ -543,15 +543,17 @@ ERROR:  REPACK (CONCURRENTLY) is not supported for partitioned tables
 HINT:  Consider running the command on individual partitions.
 DROP TABLE clstrpart;
 -- Ownership of partitions is checked
-CREATE TABLE ptnowner(i int unique) PARTITION BY LIST (i);
+CREATE TABLE ptnowner(i int unique not null) PARTITION BY LIST (i);
 CREATE INDEX ptnowner_i_idx ON ptnowner(i);
 CREATE TABLE ptnowner1 PARTITION OF ptnowner FOR VALUES IN (1);
-CREATE ROLE regress_ptnowner;
+CREATE ROLE regress_ptnowner LOGIN;
 CREATE TABLE ptnowner2 PARTITION OF ptnowner FOR VALUES IN (2);
 ALTER TABLE ptnowner1 OWNER TO regress_ptnowner;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
 ERROR:  permission denied for table ptnowner
+ALTER TABLE ptnowner1 REPLICA IDENTITY USING INDEX ptnowner1_i_key;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
 ALTER TABLE ptnowner OWNER TO regress_ptnowner;
 CREATE TEMP TABLE ptnowner_oldnodes AS
@@ -560,6 +562,11 @@ CREATE TEMP TABLE ptnowner_oldnodes AS
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
 WARNING:  permission denied to execute CLUSTER on "ptnowner2", skipping it
+-- still can't repack without a replica identity
+ALTER TABLE ptnowner1 REPLICA IDENTITY DEFAULT;
+REPACK (CONCURRENTLY) ptnowner1;
+ERROR:  cannot process relation "ptnowner1"
+HINT:  Relation "ptnowner1" has no identity index.
 RESET SESSION AUTHORIZATION;
 SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
   JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
@@ -570,6 +577,15 @@ SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
  ptnowner2 | t
 (3 rows)
 
+SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
+  JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
+  relname  | ?column? 
+-----------+----------
+ ptnowner  | t
+ ptnowner1 | f
+ ptnowner2 | t
+(3 rows)
+
 DROP TABLE ptnowner;
 DROP ROLE regress_ptnowner;
 -- Test CLUSTER with external tuplesorting
diff --git a/src/test/regress/sql/cluster.sql b/src/test/regress/sql/cluster.sql
index d14063a9683..1f471a8821a 100644
--- a/src/test/regress/sql/cluster.sql
+++ b/src/test/regress/sql/cluster.sql
@@ -254,14 +254,16 @@ REPACK (CONCURRENTLY) clstrpart;
 DROP TABLE clstrpart;
 
 -- Ownership of partitions is checked
-CREATE TABLE ptnowner(i int unique) PARTITION BY LIST (i);
+CREATE TABLE ptnowner(i int unique not null) PARTITION BY LIST (i);
 CREATE INDEX ptnowner_i_idx ON ptnowner(i);
 CREATE TABLE ptnowner1 PARTITION OF ptnowner FOR VALUES IN (1);
-CREATE ROLE regress_ptnowner;
+CREATE ROLE regress_ptnowner LOGIN;
 CREATE TABLE ptnowner2 PARTITION OF ptnowner FOR VALUES IN (2);
 ALTER TABLE ptnowner1 OWNER TO regress_ptnowner;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
+ALTER TABLE ptnowner1 REPLICA IDENTITY USING INDEX ptnowner1_i_key;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
 ALTER TABLE ptnowner OWNER TO regress_ptnowner;
 CREATE TEMP TABLE ptnowner_oldnodes AS
@@ -269,7 +271,12 @@ CREATE TEMP TABLE ptnowner_oldnodes AS
   JOIN pg_class AS c ON c.oid=tree.relid;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
+-- still can't repack without a replica identity
+ALTER TABLE ptnowner1 REPLICA IDENTITY DEFAULT;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
+SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
+  JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
 SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
   JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
 DROP TABLE ptnowner;
-- 
2.47.3


--m6zy3l65ushq557m
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-REPACK-do-not-require-LOGIN-privileges.patch"



^ permalink  raw  reply  [nested|flat] 58+ messages in thread

* [PATCH 1/2] REPACK: do not require the user to have REPLICATION
@ 2026-04-20 09:38  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 58+ messages in thread

From: Álvaro Herrera @ 2026-04-20 09:38 UTC (permalink / raw)

Although REPACK (CONCURRENTLY) uses replication slots, there is no
concern that the slot will leak data of other users, because the
MAINTAIN privilege on the table is required anyway; requiring
REPLICATION is user-unfriendly without providing any actual protection.

A related aspect is that the REPLICATION attribute is not needed to
prevent REPACK from stealing slots from logical replication, since
commit e76d8c749c31 made REPACK use a separate pool of replication
slots.

Because there are now successful concurrent repack runs in the
regression tests, we're forced to run test_plan_advice under
wal_level=replica.

Author: Antonin Houska <[email protected]>
Reported-by: Justin Pryzby <[email protected]>
Reviewed-by: Chao Li <[email protected]>
Discussion: https://postgr.es/m/aeJHPNmL4vVy3oPw@pryzbyj2023
---
 src/backend/commands/repack_worker.c          |  1 -
 .../test_plan_advice/t/001_replan_regress.pl  |  1 +
 src/test/regress/expected/cluster.out         | 20 +++++++++++++++++--
 src/test/regress/sql/cluster.sql              | 11 ++++++++--
 4 files changed, 28 insertions(+), 5 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index b17edd771e2..e4a4860805b 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -214,7 +214,6 @@ repack_setup_logical_decoding(Oid relid)
 	/*
 	 * Make sure we can use logical decoding.
 	 */
-	CheckSlotPermissions();
 	CheckLogicalDecodingRequirements(true);
 
 	/*
diff --git a/src/test/modules/test_plan_advice/t/001_replan_regress.pl b/src/test/modules/test_plan_advice/t/001_replan_regress.pl
index 38ffa4d11ae..452b179a665 100644
--- a/src/test/modules/test_plan_advice/t/001_replan_regress.pl
+++ b/src/test/modules/test_plan_advice/t/001_replan_regress.pl
@@ -18,6 +18,7 @@ $node->init();
 # Set up our desired configuration.
 $node->append_conf('postgresql.conf', <<EOM);
 shared_preload_libraries='test_plan_advice'
+wal_level=replica
 pg_plan_advice.always_explain_supplied_advice=false
 pg_plan_advice.feedback_warnings=true
 EOM
diff --git a/src/test/regress/expected/cluster.out b/src/test/regress/expected/cluster.out
index 6127b215a86..e17bc91fae1 100644
--- a/src/test/regress/expected/cluster.out
+++ b/src/test/regress/expected/cluster.out
@@ -543,15 +543,17 @@ ERROR:  REPACK (CONCURRENTLY) is not supported for partitioned tables
 HINT:  Consider running the command on individual partitions.
 DROP TABLE clstrpart;
 -- Ownership of partitions is checked
-CREATE TABLE ptnowner(i int unique) PARTITION BY LIST (i);
+CREATE TABLE ptnowner(i int unique not null) PARTITION BY LIST (i);
 CREATE INDEX ptnowner_i_idx ON ptnowner(i);
 CREATE TABLE ptnowner1 PARTITION OF ptnowner FOR VALUES IN (1);
-CREATE ROLE regress_ptnowner;
+CREATE ROLE regress_ptnowner LOGIN;
 CREATE TABLE ptnowner2 PARTITION OF ptnowner FOR VALUES IN (2);
 ALTER TABLE ptnowner1 OWNER TO regress_ptnowner;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
 ERROR:  permission denied for table ptnowner
+ALTER TABLE ptnowner1 REPLICA IDENTITY USING INDEX ptnowner1_i_key;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
 ALTER TABLE ptnowner OWNER TO regress_ptnowner;
 CREATE TEMP TABLE ptnowner_oldnodes AS
@@ -560,6 +562,11 @@ CREATE TEMP TABLE ptnowner_oldnodes AS
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
 WARNING:  permission denied to execute CLUSTER on "ptnowner2", skipping it
+-- still can't repack without a replica identity
+ALTER TABLE ptnowner1 REPLICA IDENTITY DEFAULT;
+REPACK (CONCURRENTLY) ptnowner1;
+ERROR:  cannot process relation "ptnowner1"
+HINT:  Relation "ptnowner1" has no identity index.
 RESET SESSION AUTHORIZATION;
 SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
   JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
@@ -570,6 +577,15 @@ SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
  ptnowner2 | t
 (3 rows)
 
+SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
+  JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
+  relname  | ?column? 
+-----------+----------
+ ptnowner  | t
+ ptnowner1 | f
+ ptnowner2 | t
+(3 rows)
+
 DROP TABLE ptnowner;
 DROP ROLE regress_ptnowner;
 -- Test CLUSTER with external tuplesorting
diff --git a/src/test/regress/sql/cluster.sql b/src/test/regress/sql/cluster.sql
index d14063a9683..1f471a8821a 100644
--- a/src/test/regress/sql/cluster.sql
+++ b/src/test/regress/sql/cluster.sql
@@ -254,14 +254,16 @@ REPACK (CONCURRENTLY) clstrpart;
 DROP TABLE clstrpart;
 
 -- Ownership of partitions is checked
-CREATE TABLE ptnowner(i int unique) PARTITION BY LIST (i);
+CREATE TABLE ptnowner(i int unique not null) PARTITION BY LIST (i);
 CREATE INDEX ptnowner_i_idx ON ptnowner(i);
 CREATE TABLE ptnowner1 PARTITION OF ptnowner FOR VALUES IN (1);
-CREATE ROLE regress_ptnowner;
+CREATE ROLE regress_ptnowner LOGIN;
 CREATE TABLE ptnowner2 PARTITION OF ptnowner FOR VALUES IN (2);
 ALTER TABLE ptnowner1 OWNER TO regress_ptnowner;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
+ALTER TABLE ptnowner1 REPLICA IDENTITY USING INDEX ptnowner1_i_key;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
 ALTER TABLE ptnowner OWNER TO regress_ptnowner;
 CREATE TEMP TABLE ptnowner_oldnodes AS
@@ -269,7 +271,12 @@ CREATE TEMP TABLE ptnowner_oldnodes AS
   JOIN pg_class AS c ON c.oid=tree.relid;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
+-- still can't repack without a replica identity
+ALTER TABLE ptnowner1 REPLICA IDENTITY DEFAULT;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
+SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
+  JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
 SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
   JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
 DROP TABLE ptnowner;
-- 
2.47.3


--m6zy3l65ushq557m
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-REPACK-do-not-require-LOGIN-privileges.patch"



^ permalink  raw  reply  [nested|flat] 58+ messages in thread

* [PATCH 1/2] REPACK: do not require the user to have REPLICATION
@ 2026-04-20 09:38  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 58+ messages in thread

From: Álvaro Herrera @ 2026-04-20 09:38 UTC (permalink / raw)

Although REPACK (CONCURRENTLY) uses replication slots, there is no
concern that the slot will leak data of other users, because the
MAINTAIN privilege on the table is required anyway; requiring
REPLICATION is user-unfriendly without providing any actual protection.

A related aspect is that the REPLICATION attribute is not needed to
prevent REPACK from stealing slots from logical replication, since
commit e76d8c749c31 made REPACK use a separate pool of replication
slots.

Because there are now successful concurrent repack runs in the
regression tests, we're forced to run test_plan_advice under
wal_level=replica.

Author: Antonin Houska <[email protected]>
Reported-by: Justin Pryzby <[email protected]>
Reviewed-by: Chao Li <[email protected]>
Discussion: https://postgr.es/m/aeJHPNmL4vVy3oPw@pryzbyj2023
---
 src/backend/commands/repack_worker.c          |  1 -
 .../test_plan_advice/t/001_replan_regress.pl  |  1 +
 src/test/regress/expected/cluster.out         | 20 +++++++++++++++++--
 src/test/regress/sql/cluster.sql              | 11 ++++++++--
 4 files changed, 28 insertions(+), 5 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index b17edd771e2..e4a4860805b 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -214,7 +214,6 @@ repack_setup_logical_decoding(Oid relid)
 	/*
 	 * Make sure we can use logical decoding.
 	 */
-	CheckSlotPermissions();
 	CheckLogicalDecodingRequirements(true);
 
 	/*
diff --git a/src/test/modules/test_plan_advice/t/001_replan_regress.pl b/src/test/modules/test_plan_advice/t/001_replan_regress.pl
index 38ffa4d11ae..452b179a665 100644
--- a/src/test/modules/test_plan_advice/t/001_replan_regress.pl
+++ b/src/test/modules/test_plan_advice/t/001_replan_regress.pl
@@ -18,6 +18,7 @@ $node->init();
 # Set up our desired configuration.
 $node->append_conf('postgresql.conf', <<EOM);
 shared_preload_libraries='test_plan_advice'
+wal_level=replica
 pg_plan_advice.always_explain_supplied_advice=false
 pg_plan_advice.feedback_warnings=true
 EOM
diff --git a/src/test/regress/expected/cluster.out b/src/test/regress/expected/cluster.out
index 6127b215a86..e17bc91fae1 100644
--- a/src/test/regress/expected/cluster.out
+++ b/src/test/regress/expected/cluster.out
@@ -543,15 +543,17 @@ ERROR:  REPACK (CONCURRENTLY) is not supported for partitioned tables
 HINT:  Consider running the command on individual partitions.
 DROP TABLE clstrpart;
 -- Ownership of partitions is checked
-CREATE TABLE ptnowner(i int unique) PARTITION BY LIST (i);
+CREATE TABLE ptnowner(i int unique not null) PARTITION BY LIST (i);
 CREATE INDEX ptnowner_i_idx ON ptnowner(i);
 CREATE TABLE ptnowner1 PARTITION OF ptnowner FOR VALUES IN (1);
-CREATE ROLE regress_ptnowner;
+CREATE ROLE regress_ptnowner LOGIN;
 CREATE TABLE ptnowner2 PARTITION OF ptnowner FOR VALUES IN (2);
 ALTER TABLE ptnowner1 OWNER TO regress_ptnowner;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
 ERROR:  permission denied for table ptnowner
+ALTER TABLE ptnowner1 REPLICA IDENTITY USING INDEX ptnowner1_i_key;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
 ALTER TABLE ptnowner OWNER TO regress_ptnowner;
 CREATE TEMP TABLE ptnowner_oldnodes AS
@@ -560,6 +562,11 @@ CREATE TEMP TABLE ptnowner_oldnodes AS
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
 WARNING:  permission denied to execute CLUSTER on "ptnowner2", skipping it
+-- still can't repack without a replica identity
+ALTER TABLE ptnowner1 REPLICA IDENTITY DEFAULT;
+REPACK (CONCURRENTLY) ptnowner1;
+ERROR:  cannot process relation "ptnowner1"
+HINT:  Relation "ptnowner1" has no identity index.
 RESET SESSION AUTHORIZATION;
 SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
   JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
@@ -570,6 +577,15 @@ SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
  ptnowner2 | t
 (3 rows)
 
+SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
+  JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
+  relname  | ?column? 
+-----------+----------
+ ptnowner  | t
+ ptnowner1 | f
+ ptnowner2 | t
+(3 rows)
+
 DROP TABLE ptnowner;
 DROP ROLE regress_ptnowner;
 -- Test CLUSTER with external tuplesorting
diff --git a/src/test/regress/sql/cluster.sql b/src/test/regress/sql/cluster.sql
index d14063a9683..1f471a8821a 100644
--- a/src/test/regress/sql/cluster.sql
+++ b/src/test/regress/sql/cluster.sql
@@ -254,14 +254,16 @@ REPACK (CONCURRENTLY) clstrpart;
 DROP TABLE clstrpart;
 
 -- Ownership of partitions is checked
-CREATE TABLE ptnowner(i int unique) PARTITION BY LIST (i);
+CREATE TABLE ptnowner(i int unique not null) PARTITION BY LIST (i);
 CREATE INDEX ptnowner_i_idx ON ptnowner(i);
 CREATE TABLE ptnowner1 PARTITION OF ptnowner FOR VALUES IN (1);
-CREATE ROLE regress_ptnowner;
+CREATE ROLE regress_ptnowner LOGIN;
 CREATE TABLE ptnowner2 PARTITION OF ptnowner FOR VALUES IN (2);
 ALTER TABLE ptnowner1 OWNER TO regress_ptnowner;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
+ALTER TABLE ptnowner1 REPLICA IDENTITY USING INDEX ptnowner1_i_key;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
 ALTER TABLE ptnowner OWNER TO regress_ptnowner;
 CREATE TEMP TABLE ptnowner_oldnodes AS
@@ -269,7 +271,12 @@ CREATE TEMP TABLE ptnowner_oldnodes AS
   JOIN pg_class AS c ON c.oid=tree.relid;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
+-- still can't repack without a replica identity
+ALTER TABLE ptnowner1 REPLICA IDENTITY DEFAULT;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
+SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
+  JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
 SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
   JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
 DROP TABLE ptnowner;
-- 
2.47.3


--m6zy3l65ushq557m
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-REPACK-do-not-require-LOGIN-privileges.patch"



^ permalink  raw  reply  [nested|flat] 58+ messages in thread

* [PATCH 1/2] REPACK: do not require the user to have REPLICATION
@ 2026-04-20 09:38  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 58+ messages in thread

From: Álvaro Herrera @ 2026-04-20 09:38 UTC (permalink / raw)

Although REPACK (CONCURRENTLY) uses replication slots, there is no
concern that the slot will leak data of other users, because the
MAINTAIN privilege on the table is required anyway; requiring
REPLICATION is user-unfriendly without providing any actual protection.

A related aspect is that the REPLICATION attribute is not needed to
prevent REPACK from stealing slots from logical replication, since
commit e76d8c749c31 made REPACK use a separate pool of replication
slots.

Because there are now successful concurrent repack runs in the
regression tests, we're forced to run test_plan_advice under
wal_level=replica.

Author: Antonin Houska <[email protected]>
Reported-by: Justin Pryzby <[email protected]>
Reviewed-by: Chao Li <[email protected]>
Discussion: https://postgr.es/m/aeJHPNmL4vVy3oPw@pryzbyj2023
---
 src/backend/commands/repack_worker.c          |  1 -
 .../test_plan_advice/t/001_replan_regress.pl  |  1 +
 src/test/regress/expected/cluster.out         | 20 +++++++++++++++++--
 src/test/regress/sql/cluster.sql              | 11 ++++++++--
 4 files changed, 28 insertions(+), 5 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index b17edd771e2..e4a4860805b 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -214,7 +214,6 @@ repack_setup_logical_decoding(Oid relid)
 	/*
 	 * Make sure we can use logical decoding.
 	 */
-	CheckSlotPermissions();
 	CheckLogicalDecodingRequirements(true);
 
 	/*
diff --git a/src/test/modules/test_plan_advice/t/001_replan_regress.pl b/src/test/modules/test_plan_advice/t/001_replan_regress.pl
index 38ffa4d11ae..452b179a665 100644
--- a/src/test/modules/test_plan_advice/t/001_replan_regress.pl
+++ b/src/test/modules/test_plan_advice/t/001_replan_regress.pl
@@ -18,6 +18,7 @@ $node->init();
 # Set up our desired configuration.
 $node->append_conf('postgresql.conf', <<EOM);
 shared_preload_libraries='test_plan_advice'
+wal_level=replica
 pg_plan_advice.always_explain_supplied_advice=false
 pg_plan_advice.feedback_warnings=true
 EOM
diff --git a/src/test/regress/expected/cluster.out b/src/test/regress/expected/cluster.out
index 6127b215a86..e17bc91fae1 100644
--- a/src/test/regress/expected/cluster.out
+++ b/src/test/regress/expected/cluster.out
@@ -543,15 +543,17 @@ ERROR:  REPACK (CONCURRENTLY) is not supported for partitioned tables
 HINT:  Consider running the command on individual partitions.
 DROP TABLE clstrpart;
 -- Ownership of partitions is checked
-CREATE TABLE ptnowner(i int unique) PARTITION BY LIST (i);
+CREATE TABLE ptnowner(i int unique not null) PARTITION BY LIST (i);
 CREATE INDEX ptnowner_i_idx ON ptnowner(i);
 CREATE TABLE ptnowner1 PARTITION OF ptnowner FOR VALUES IN (1);
-CREATE ROLE regress_ptnowner;
+CREATE ROLE regress_ptnowner LOGIN;
 CREATE TABLE ptnowner2 PARTITION OF ptnowner FOR VALUES IN (2);
 ALTER TABLE ptnowner1 OWNER TO regress_ptnowner;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
 ERROR:  permission denied for table ptnowner
+ALTER TABLE ptnowner1 REPLICA IDENTITY USING INDEX ptnowner1_i_key;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
 ALTER TABLE ptnowner OWNER TO regress_ptnowner;
 CREATE TEMP TABLE ptnowner_oldnodes AS
@@ -560,6 +562,11 @@ CREATE TEMP TABLE ptnowner_oldnodes AS
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
 WARNING:  permission denied to execute CLUSTER on "ptnowner2", skipping it
+-- still can't repack without a replica identity
+ALTER TABLE ptnowner1 REPLICA IDENTITY DEFAULT;
+REPACK (CONCURRENTLY) ptnowner1;
+ERROR:  cannot process relation "ptnowner1"
+HINT:  Relation "ptnowner1" has no identity index.
 RESET SESSION AUTHORIZATION;
 SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
   JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
@@ -570,6 +577,15 @@ SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
  ptnowner2 | t
 (3 rows)
 
+SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
+  JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
+  relname  | ?column? 
+-----------+----------
+ ptnowner  | t
+ ptnowner1 | f
+ ptnowner2 | t
+(3 rows)
+
 DROP TABLE ptnowner;
 DROP ROLE regress_ptnowner;
 -- Test CLUSTER with external tuplesorting
diff --git a/src/test/regress/sql/cluster.sql b/src/test/regress/sql/cluster.sql
index d14063a9683..1f471a8821a 100644
--- a/src/test/regress/sql/cluster.sql
+++ b/src/test/regress/sql/cluster.sql
@@ -254,14 +254,16 @@ REPACK (CONCURRENTLY) clstrpart;
 DROP TABLE clstrpart;
 
 -- Ownership of partitions is checked
-CREATE TABLE ptnowner(i int unique) PARTITION BY LIST (i);
+CREATE TABLE ptnowner(i int unique not null) PARTITION BY LIST (i);
 CREATE INDEX ptnowner_i_idx ON ptnowner(i);
 CREATE TABLE ptnowner1 PARTITION OF ptnowner FOR VALUES IN (1);
-CREATE ROLE regress_ptnowner;
+CREATE ROLE regress_ptnowner LOGIN;
 CREATE TABLE ptnowner2 PARTITION OF ptnowner FOR VALUES IN (2);
 ALTER TABLE ptnowner1 OWNER TO regress_ptnowner;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
+ALTER TABLE ptnowner1 REPLICA IDENTITY USING INDEX ptnowner1_i_key;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
 ALTER TABLE ptnowner OWNER TO regress_ptnowner;
 CREATE TEMP TABLE ptnowner_oldnodes AS
@@ -269,7 +271,12 @@ CREATE TEMP TABLE ptnowner_oldnodes AS
   JOIN pg_class AS c ON c.oid=tree.relid;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
+-- still can't repack without a replica identity
+ALTER TABLE ptnowner1 REPLICA IDENTITY DEFAULT;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
+SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
+  JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
 SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
   JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
 DROP TABLE ptnowner;
-- 
2.47.3


--m6zy3l65ushq557m
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-REPACK-do-not-require-LOGIN-privileges.patch"



^ permalink  raw  reply  [nested|flat] 58+ messages in thread

* [PATCH 1/2] REPACK: do not require the user to have REPLICATION
@ 2026-04-20 09:38  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 58+ messages in thread

From: Álvaro Herrera @ 2026-04-20 09:38 UTC (permalink / raw)

Although REPACK (CONCURRENTLY) uses replication slots, there is no
concern that the slot will leak data of other users, because the
MAINTAIN privilege on the table is required anyway; requiring
REPLICATION is user-unfriendly without providing any actual protection.

A related aspect is that the REPLICATION attribute is not needed to
prevent REPACK from stealing slots from logical replication, since
commit e76d8c749c31 made REPACK use a separate pool of replication
slots.

Because there are now successful concurrent repack runs in the
regression tests, we're forced to run test_plan_advice under
wal_level=replica.

Author: Antonin Houska <[email protected]>
Reported-by: Justin Pryzby <[email protected]>
Reviewed-by: Chao Li <[email protected]>
Discussion: https://postgr.es/m/aeJHPNmL4vVy3oPw@pryzbyj2023
---
 src/backend/commands/repack_worker.c          |  1 -
 .../test_plan_advice/t/001_replan_regress.pl  |  1 +
 src/test/regress/expected/cluster.out         | 20 +++++++++++++++++--
 src/test/regress/sql/cluster.sql              | 11 ++++++++--
 4 files changed, 28 insertions(+), 5 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index b17edd771e2..e4a4860805b 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -214,7 +214,6 @@ repack_setup_logical_decoding(Oid relid)
 	/*
 	 * Make sure we can use logical decoding.
 	 */
-	CheckSlotPermissions();
 	CheckLogicalDecodingRequirements(true);
 
 	/*
diff --git a/src/test/modules/test_plan_advice/t/001_replan_regress.pl b/src/test/modules/test_plan_advice/t/001_replan_regress.pl
index 38ffa4d11ae..452b179a665 100644
--- a/src/test/modules/test_plan_advice/t/001_replan_regress.pl
+++ b/src/test/modules/test_plan_advice/t/001_replan_regress.pl
@@ -18,6 +18,7 @@ $node->init();
 # Set up our desired configuration.
 $node->append_conf('postgresql.conf', <<EOM);
 shared_preload_libraries='test_plan_advice'
+wal_level=replica
 pg_plan_advice.always_explain_supplied_advice=false
 pg_plan_advice.feedback_warnings=true
 EOM
diff --git a/src/test/regress/expected/cluster.out b/src/test/regress/expected/cluster.out
index 6127b215a86..e17bc91fae1 100644
--- a/src/test/regress/expected/cluster.out
+++ b/src/test/regress/expected/cluster.out
@@ -543,15 +543,17 @@ ERROR:  REPACK (CONCURRENTLY) is not supported for partitioned tables
 HINT:  Consider running the command on individual partitions.
 DROP TABLE clstrpart;
 -- Ownership of partitions is checked
-CREATE TABLE ptnowner(i int unique) PARTITION BY LIST (i);
+CREATE TABLE ptnowner(i int unique not null) PARTITION BY LIST (i);
 CREATE INDEX ptnowner_i_idx ON ptnowner(i);
 CREATE TABLE ptnowner1 PARTITION OF ptnowner FOR VALUES IN (1);
-CREATE ROLE regress_ptnowner;
+CREATE ROLE regress_ptnowner LOGIN;
 CREATE TABLE ptnowner2 PARTITION OF ptnowner FOR VALUES IN (2);
 ALTER TABLE ptnowner1 OWNER TO regress_ptnowner;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
 ERROR:  permission denied for table ptnowner
+ALTER TABLE ptnowner1 REPLICA IDENTITY USING INDEX ptnowner1_i_key;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
 ALTER TABLE ptnowner OWNER TO regress_ptnowner;
 CREATE TEMP TABLE ptnowner_oldnodes AS
@@ -560,6 +562,11 @@ CREATE TEMP TABLE ptnowner_oldnodes AS
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
 WARNING:  permission denied to execute CLUSTER on "ptnowner2", skipping it
+-- still can't repack without a replica identity
+ALTER TABLE ptnowner1 REPLICA IDENTITY DEFAULT;
+REPACK (CONCURRENTLY) ptnowner1;
+ERROR:  cannot process relation "ptnowner1"
+HINT:  Relation "ptnowner1" has no identity index.
 RESET SESSION AUTHORIZATION;
 SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
   JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
@@ -570,6 +577,15 @@ SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
  ptnowner2 | t
 (3 rows)
 
+SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
+  JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
+  relname  | ?column? 
+-----------+----------
+ ptnowner  | t
+ ptnowner1 | f
+ ptnowner2 | t
+(3 rows)
+
 DROP TABLE ptnowner;
 DROP ROLE regress_ptnowner;
 -- Test CLUSTER with external tuplesorting
diff --git a/src/test/regress/sql/cluster.sql b/src/test/regress/sql/cluster.sql
index d14063a9683..1f471a8821a 100644
--- a/src/test/regress/sql/cluster.sql
+++ b/src/test/regress/sql/cluster.sql
@@ -254,14 +254,16 @@ REPACK (CONCURRENTLY) clstrpart;
 DROP TABLE clstrpart;
 
 -- Ownership of partitions is checked
-CREATE TABLE ptnowner(i int unique) PARTITION BY LIST (i);
+CREATE TABLE ptnowner(i int unique not null) PARTITION BY LIST (i);
 CREATE INDEX ptnowner_i_idx ON ptnowner(i);
 CREATE TABLE ptnowner1 PARTITION OF ptnowner FOR VALUES IN (1);
-CREATE ROLE regress_ptnowner;
+CREATE ROLE regress_ptnowner LOGIN;
 CREATE TABLE ptnowner2 PARTITION OF ptnowner FOR VALUES IN (2);
 ALTER TABLE ptnowner1 OWNER TO regress_ptnowner;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
+ALTER TABLE ptnowner1 REPLICA IDENTITY USING INDEX ptnowner1_i_key;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
 ALTER TABLE ptnowner OWNER TO regress_ptnowner;
 CREATE TEMP TABLE ptnowner_oldnodes AS
@@ -269,7 +271,12 @@ CREATE TEMP TABLE ptnowner_oldnodes AS
   JOIN pg_class AS c ON c.oid=tree.relid;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
+-- still can't repack without a replica identity
+ALTER TABLE ptnowner1 REPLICA IDENTITY DEFAULT;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
+SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
+  JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
 SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
   JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
 DROP TABLE ptnowner;
-- 
2.47.3


--m6zy3l65ushq557m
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-REPACK-do-not-require-LOGIN-privileges.patch"



^ permalink  raw  reply  [nested|flat] 58+ messages in thread

* [PATCH 1/2] REPACK: do not require the user to have REPLICATION
@ 2026-04-20 09:38  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 58+ messages in thread

From: Álvaro Herrera @ 2026-04-20 09:38 UTC (permalink / raw)

Although REPACK (CONCURRENTLY) uses replication slots, there is no
concern that the slot will leak data of other users, because the
MAINTAIN privilege on the table is required anyway; requiring
REPLICATION is user-unfriendly without providing any actual protection.

A related aspect is that the REPLICATION attribute is not needed to
prevent REPACK from stealing slots from logical replication, since
commit e76d8c749c31 made REPACK use a separate pool of replication
slots.

Because there are now successful concurrent repack runs in the
regression tests, we're forced to run test_plan_advice under
wal_level=replica.

Author: Antonin Houska <[email protected]>
Reported-by: Justin Pryzby <[email protected]>
Reviewed-by: Chao Li <[email protected]>
Discussion: https://postgr.es/m/aeJHPNmL4vVy3oPw@pryzbyj2023
---
 src/backend/commands/repack_worker.c          |  1 -
 .../test_plan_advice/t/001_replan_regress.pl  |  1 +
 src/test/regress/expected/cluster.out         | 20 +++++++++++++++++--
 src/test/regress/sql/cluster.sql              | 11 ++++++++--
 4 files changed, 28 insertions(+), 5 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index b17edd771e2..e4a4860805b 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -214,7 +214,6 @@ repack_setup_logical_decoding(Oid relid)
 	/*
 	 * Make sure we can use logical decoding.
 	 */
-	CheckSlotPermissions();
 	CheckLogicalDecodingRequirements(true);
 
 	/*
diff --git a/src/test/modules/test_plan_advice/t/001_replan_regress.pl b/src/test/modules/test_plan_advice/t/001_replan_regress.pl
index 38ffa4d11ae..452b179a665 100644
--- a/src/test/modules/test_plan_advice/t/001_replan_regress.pl
+++ b/src/test/modules/test_plan_advice/t/001_replan_regress.pl
@@ -18,6 +18,7 @@ $node->init();
 # Set up our desired configuration.
 $node->append_conf('postgresql.conf', <<EOM);
 shared_preload_libraries='test_plan_advice'
+wal_level=replica
 pg_plan_advice.always_explain_supplied_advice=false
 pg_plan_advice.feedback_warnings=true
 EOM
diff --git a/src/test/regress/expected/cluster.out b/src/test/regress/expected/cluster.out
index 6127b215a86..e17bc91fae1 100644
--- a/src/test/regress/expected/cluster.out
+++ b/src/test/regress/expected/cluster.out
@@ -543,15 +543,17 @@ ERROR:  REPACK (CONCURRENTLY) is not supported for partitioned tables
 HINT:  Consider running the command on individual partitions.
 DROP TABLE clstrpart;
 -- Ownership of partitions is checked
-CREATE TABLE ptnowner(i int unique) PARTITION BY LIST (i);
+CREATE TABLE ptnowner(i int unique not null) PARTITION BY LIST (i);
 CREATE INDEX ptnowner_i_idx ON ptnowner(i);
 CREATE TABLE ptnowner1 PARTITION OF ptnowner FOR VALUES IN (1);
-CREATE ROLE regress_ptnowner;
+CREATE ROLE regress_ptnowner LOGIN;
 CREATE TABLE ptnowner2 PARTITION OF ptnowner FOR VALUES IN (2);
 ALTER TABLE ptnowner1 OWNER TO regress_ptnowner;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
 ERROR:  permission denied for table ptnowner
+ALTER TABLE ptnowner1 REPLICA IDENTITY USING INDEX ptnowner1_i_key;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
 ALTER TABLE ptnowner OWNER TO regress_ptnowner;
 CREATE TEMP TABLE ptnowner_oldnodes AS
@@ -560,6 +562,11 @@ CREATE TEMP TABLE ptnowner_oldnodes AS
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
 WARNING:  permission denied to execute CLUSTER on "ptnowner2", skipping it
+-- still can't repack without a replica identity
+ALTER TABLE ptnowner1 REPLICA IDENTITY DEFAULT;
+REPACK (CONCURRENTLY) ptnowner1;
+ERROR:  cannot process relation "ptnowner1"
+HINT:  Relation "ptnowner1" has no identity index.
 RESET SESSION AUTHORIZATION;
 SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
   JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
@@ -570,6 +577,15 @@ SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
  ptnowner2 | t
 (3 rows)
 
+SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
+  JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
+  relname  | ?column? 
+-----------+----------
+ ptnowner  | t
+ ptnowner1 | f
+ ptnowner2 | t
+(3 rows)
+
 DROP TABLE ptnowner;
 DROP ROLE regress_ptnowner;
 -- Test CLUSTER with external tuplesorting
diff --git a/src/test/regress/sql/cluster.sql b/src/test/regress/sql/cluster.sql
index d14063a9683..1f471a8821a 100644
--- a/src/test/regress/sql/cluster.sql
+++ b/src/test/regress/sql/cluster.sql
@@ -254,14 +254,16 @@ REPACK (CONCURRENTLY) clstrpart;
 DROP TABLE clstrpart;
 
 -- Ownership of partitions is checked
-CREATE TABLE ptnowner(i int unique) PARTITION BY LIST (i);
+CREATE TABLE ptnowner(i int unique not null) PARTITION BY LIST (i);
 CREATE INDEX ptnowner_i_idx ON ptnowner(i);
 CREATE TABLE ptnowner1 PARTITION OF ptnowner FOR VALUES IN (1);
-CREATE ROLE regress_ptnowner;
+CREATE ROLE regress_ptnowner LOGIN;
 CREATE TABLE ptnowner2 PARTITION OF ptnowner FOR VALUES IN (2);
 ALTER TABLE ptnowner1 OWNER TO regress_ptnowner;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
+ALTER TABLE ptnowner1 REPLICA IDENTITY USING INDEX ptnowner1_i_key;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
 ALTER TABLE ptnowner OWNER TO regress_ptnowner;
 CREATE TEMP TABLE ptnowner_oldnodes AS
@@ -269,7 +271,12 @@ CREATE TEMP TABLE ptnowner_oldnodes AS
   JOIN pg_class AS c ON c.oid=tree.relid;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
+-- still can't repack without a replica identity
+ALTER TABLE ptnowner1 REPLICA IDENTITY DEFAULT;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
+SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
+  JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
 SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
   JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
 DROP TABLE ptnowner;
-- 
2.47.3


--m6zy3l65ushq557m
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-REPACK-do-not-require-LOGIN-privileges.patch"



^ permalink  raw  reply  [nested|flat] 58+ messages in thread

* [PATCH 1/2] REPACK: do not require the user to have REPLICATION
@ 2026-04-20 09:38  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 58+ messages in thread

From: Álvaro Herrera @ 2026-04-20 09:38 UTC (permalink / raw)

Although REPACK (CONCURRENTLY) uses replication slots, there is no
concern that the slot will leak data of other users, because the
MAINTAIN privilege on the table is required anyway; requiring
REPLICATION is user-unfriendly without providing any actual protection.

A related aspect is that the REPLICATION attribute is not needed to
prevent REPACK from stealing slots from logical replication, since
commit e76d8c749c31 made REPACK use a separate pool of replication
slots.

Because there are now successful concurrent repack runs in the
regression tests, we're forced to run test_plan_advice under
wal_level=replica.

Author: Antonin Houska <[email protected]>
Reported-by: Justin Pryzby <[email protected]>
Reviewed-by: Chao Li <[email protected]>
Discussion: https://postgr.es/m/aeJHPNmL4vVy3oPw@pryzbyj2023
---
 src/backend/commands/repack_worker.c          |  1 -
 .../test_plan_advice/t/001_replan_regress.pl  |  1 +
 src/test/regress/expected/cluster.out         | 20 +++++++++++++++++--
 src/test/regress/sql/cluster.sql              | 11 ++++++++--
 4 files changed, 28 insertions(+), 5 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index b17edd771e2..e4a4860805b 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -214,7 +214,6 @@ repack_setup_logical_decoding(Oid relid)
 	/*
 	 * Make sure we can use logical decoding.
 	 */
-	CheckSlotPermissions();
 	CheckLogicalDecodingRequirements(true);
 
 	/*
diff --git a/src/test/modules/test_plan_advice/t/001_replan_regress.pl b/src/test/modules/test_plan_advice/t/001_replan_regress.pl
index 38ffa4d11ae..452b179a665 100644
--- a/src/test/modules/test_plan_advice/t/001_replan_regress.pl
+++ b/src/test/modules/test_plan_advice/t/001_replan_regress.pl
@@ -18,6 +18,7 @@ $node->init();
 # Set up our desired configuration.
 $node->append_conf('postgresql.conf', <<EOM);
 shared_preload_libraries='test_plan_advice'
+wal_level=replica
 pg_plan_advice.always_explain_supplied_advice=false
 pg_plan_advice.feedback_warnings=true
 EOM
diff --git a/src/test/regress/expected/cluster.out b/src/test/regress/expected/cluster.out
index 6127b215a86..e17bc91fae1 100644
--- a/src/test/regress/expected/cluster.out
+++ b/src/test/regress/expected/cluster.out
@@ -543,15 +543,17 @@ ERROR:  REPACK (CONCURRENTLY) is not supported for partitioned tables
 HINT:  Consider running the command on individual partitions.
 DROP TABLE clstrpart;
 -- Ownership of partitions is checked
-CREATE TABLE ptnowner(i int unique) PARTITION BY LIST (i);
+CREATE TABLE ptnowner(i int unique not null) PARTITION BY LIST (i);
 CREATE INDEX ptnowner_i_idx ON ptnowner(i);
 CREATE TABLE ptnowner1 PARTITION OF ptnowner FOR VALUES IN (1);
-CREATE ROLE regress_ptnowner;
+CREATE ROLE regress_ptnowner LOGIN;
 CREATE TABLE ptnowner2 PARTITION OF ptnowner FOR VALUES IN (2);
 ALTER TABLE ptnowner1 OWNER TO regress_ptnowner;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
 ERROR:  permission denied for table ptnowner
+ALTER TABLE ptnowner1 REPLICA IDENTITY USING INDEX ptnowner1_i_key;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
 ALTER TABLE ptnowner OWNER TO regress_ptnowner;
 CREATE TEMP TABLE ptnowner_oldnodes AS
@@ -560,6 +562,11 @@ CREATE TEMP TABLE ptnowner_oldnodes AS
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
 WARNING:  permission denied to execute CLUSTER on "ptnowner2", skipping it
+-- still can't repack without a replica identity
+ALTER TABLE ptnowner1 REPLICA IDENTITY DEFAULT;
+REPACK (CONCURRENTLY) ptnowner1;
+ERROR:  cannot process relation "ptnowner1"
+HINT:  Relation "ptnowner1" has no identity index.
 RESET SESSION AUTHORIZATION;
 SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
   JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
@@ -570,6 +577,15 @@ SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
  ptnowner2 | t
 (3 rows)
 
+SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
+  JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
+  relname  | ?column? 
+-----------+----------
+ ptnowner  | t
+ ptnowner1 | f
+ ptnowner2 | t
+(3 rows)
+
 DROP TABLE ptnowner;
 DROP ROLE regress_ptnowner;
 -- Test CLUSTER with external tuplesorting
diff --git a/src/test/regress/sql/cluster.sql b/src/test/regress/sql/cluster.sql
index d14063a9683..1f471a8821a 100644
--- a/src/test/regress/sql/cluster.sql
+++ b/src/test/regress/sql/cluster.sql
@@ -254,14 +254,16 @@ REPACK (CONCURRENTLY) clstrpart;
 DROP TABLE clstrpart;
 
 -- Ownership of partitions is checked
-CREATE TABLE ptnowner(i int unique) PARTITION BY LIST (i);
+CREATE TABLE ptnowner(i int unique not null) PARTITION BY LIST (i);
 CREATE INDEX ptnowner_i_idx ON ptnowner(i);
 CREATE TABLE ptnowner1 PARTITION OF ptnowner FOR VALUES IN (1);
-CREATE ROLE regress_ptnowner;
+CREATE ROLE regress_ptnowner LOGIN;
 CREATE TABLE ptnowner2 PARTITION OF ptnowner FOR VALUES IN (2);
 ALTER TABLE ptnowner1 OWNER TO regress_ptnowner;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
+ALTER TABLE ptnowner1 REPLICA IDENTITY USING INDEX ptnowner1_i_key;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
 ALTER TABLE ptnowner OWNER TO regress_ptnowner;
 CREATE TEMP TABLE ptnowner_oldnodes AS
@@ -269,7 +271,12 @@ CREATE TEMP TABLE ptnowner_oldnodes AS
   JOIN pg_class AS c ON c.oid=tree.relid;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
+-- still can't repack without a replica identity
+ALTER TABLE ptnowner1 REPLICA IDENTITY DEFAULT;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
+SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
+  JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
 SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
   JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
 DROP TABLE ptnowner;
-- 
2.47.3


--m6zy3l65ushq557m
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-REPACK-do-not-require-LOGIN-privileges.patch"



^ permalink  raw  reply  [nested|flat] 58+ messages in thread

* [PATCH 1/2] REPACK: do not require the user to have REPLICATION
@ 2026-04-20 09:38  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 58+ messages in thread

From: Álvaro Herrera @ 2026-04-20 09:38 UTC (permalink / raw)

Although REPACK (CONCURRENTLY) uses replication slots, there is no
concern that the slot will leak data of other users, because the
MAINTAIN privilege on the table is required anyway; requiring
REPLICATION is user-unfriendly without providing any actual protection.

A related aspect is that the REPLICATION attribute is not needed to
prevent REPACK from stealing slots from logical replication, since
commit e76d8c749c31 made REPACK use a separate pool of replication
slots.

Because there are now successful concurrent repack runs in the
regression tests, we're forced to run test_plan_advice under
wal_level=replica.

Author: Antonin Houska <[email protected]>
Reported-by: Justin Pryzby <[email protected]>
Reviewed-by: Chao Li <[email protected]>
Discussion: https://postgr.es/m/aeJHPNmL4vVy3oPw@pryzbyj2023
---
 src/backend/commands/repack_worker.c          |  1 -
 .../test_plan_advice/t/001_replan_regress.pl  |  1 +
 src/test/regress/expected/cluster.out         | 20 +++++++++++++++++--
 src/test/regress/sql/cluster.sql              | 11 ++++++++--
 4 files changed, 28 insertions(+), 5 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index b17edd771e2..e4a4860805b 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -214,7 +214,6 @@ repack_setup_logical_decoding(Oid relid)
 	/*
 	 * Make sure we can use logical decoding.
 	 */
-	CheckSlotPermissions();
 	CheckLogicalDecodingRequirements(true);
 
 	/*
diff --git a/src/test/modules/test_plan_advice/t/001_replan_regress.pl b/src/test/modules/test_plan_advice/t/001_replan_regress.pl
index 38ffa4d11ae..452b179a665 100644
--- a/src/test/modules/test_plan_advice/t/001_replan_regress.pl
+++ b/src/test/modules/test_plan_advice/t/001_replan_regress.pl
@@ -18,6 +18,7 @@ $node->init();
 # Set up our desired configuration.
 $node->append_conf('postgresql.conf', <<EOM);
 shared_preload_libraries='test_plan_advice'
+wal_level=replica
 pg_plan_advice.always_explain_supplied_advice=false
 pg_plan_advice.feedback_warnings=true
 EOM
diff --git a/src/test/regress/expected/cluster.out b/src/test/regress/expected/cluster.out
index 6127b215a86..e17bc91fae1 100644
--- a/src/test/regress/expected/cluster.out
+++ b/src/test/regress/expected/cluster.out
@@ -543,15 +543,17 @@ ERROR:  REPACK (CONCURRENTLY) is not supported for partitioned tables
 HINT:  Consider running the command on individual partitions.
 DROP TABLE clstrpart;
 -- Ownership of partitions is checked
-CREATE TABLE ptnowner(i int unique) PARTITION BY LIST (i);
+CREATE TABLE ptnowner(i int unique not null) PARTITION BY LIST (i);
 CREATE INDEX ptnowner_i_idx ON ptnowner(i);
 CREATE TABLE ptnowner1 PARTITION OF ptnowner FOR VALUES IN (1);
-CREATE ROLE regress_ptnowner;
+CREATE ROLE regress_ptnowner LOGIN;
 CREATE TABLE ptnowner2 PARTITION OF ptnowner FOR VALUES IN (2);
 ALTER TABLE ptnowner1 OWNER TO regress_ptnowner;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
 ERROR:  permission denied for table ptnowner
+ALTER TABLE ptnowner1 REPLICA IDENTITY USING INDEX ptnowner1_i_key;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
 ALTER TABLE ptnowner OWNER TO regress_ptnowner;
 CREATE TEMP TABLE ptnowner_oldnodes AS
@@ -560,6 +562,11 @@ CREATE TEMP TABLE ptnowner_oldnodes AS
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
 WARNING:  permission denied to execute CLUSTER on "ptnowner2", skipping it
+-- still can't repack without a replica identity
+ALTER TABLE ptnowner1 REPLICA IDENTITY DEFAULT;
+REPACK (CONCURRENTLY) ptnowner1;
+ERROR:  cannot process relation "ptnowner1"
+HINT:  Relation "ptnowner1" has no identity index.
 RESET SESSION AUTHORIZATION;
 SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
   JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
@@ -570,6 +577,15 @@ SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
  ptnowner2 | t
 (3 rows)
 
+SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
+  JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
+  relname  | ?column? 
+-----------+----------
+ ptnowner  | t
+ ptnowner1 | f
+ ptnowner2 | t
+(3 rows)
+
 DROP TABLE ptnowner;
 DROP ROLE regress_ptnowner;
 -- Test CLUSTER with external tuplesorting
diff --git a/src/test/regress/sql/cluster.sql b/src/test/regress/sql/cluster.sql
index d14063a9683..1f471a8821a 100644
--- a/src/test/regress/sql/cluster.sql
+++ b/src/test/regress/sql/cluster.sql
@@ -254,14 +254,16 @@ REPACK (CONCURRENTLY) clstrpart;
 DROP TABLE clstrpart;
 
 -- Ownership of partitions is checked
-CREATE TABLE ptnowner(i int unique) PARTITION BY LIST (i);
+CREATE TABLE ptnowner(i int unique not null) PARTITION BY LIST (i);
 CREATE INDEX ptnowner_i_idx ON ptnowner(i);
 CREATE TABLE ptnowner1 PARTITION OF ptnowner FOR VALUES IN (1);
-CREATE ROLE regress_ptnowner;
+CREATE ROLE regress_ptnowner LOGIN;
 CREATE TABLE ptnowner2 PARTITION OF ptnowner FOR VALUES IN (2);
 ALTER TABLE ptnowner1 OWNER TO regress_ptnowner;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
+ALTER TABLE ptnowner1 REPLICA IDENTITY USING INDEX ptnowner1_i_key;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
 ALTER TABLE ptnowner OWNER TO regress_ptnowner;
 CREATE TEMP TABLE ptnowner_oldnodes AS
@@ -269,7 +271,12 @@ CREATE TEMP TABLE ptnowner_oldnodes AS
   JOIN pg_class AS c ON c.oid=tree.relid;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
+-- still can't repack without a replica identity
+ALTER TABLE ptnowner1 REPLICA IDENTITY DEFAULT;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
+SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
+  JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
 SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
   JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
 DROP TABLE ptnowner;
-- 
2.47.3


--m6zy3l65ushq557m
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-REPACK-do-not-require-LOGIN-privileges.patch"



^ permalink  raw  reply  [nested|flat] 58+ messages in thread

* [PATCH 1/2] REPACK: do not require the user to have REPLICATION
@ 2026-04-20 09:38  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 58+ messages in thread

From: Álvaro Herrera @ 2026-04-20 09:38 UTC (permalink / raw)

Although REPACK (CONCURRENTLY) uses replication slots, there is no
concern that the slot will leak data of other users, because the
MAINTAIN privilege on the table is required anyway; requiring
REPLICATION is user-unfriendly without providing any actual protection.

A related aspect is that the REPLICATION attribute is not needed to
prevent REPACK from stealing slots from logical replication, since
commit e76d8c749c31 made REPACK use a separate pool of replication
slots.

Because there are now successful concurrent repack runs in the
regression tests, we're forced to run test_plan_advice under
wal_level=replica.

Author: Antonin Houska <[email protected]>
Reported-by: Justin Pryzby <[email protected]>
Reviewed-by: Chao Li <[email protected]>
Discussion: https://postgr.es/m/aeJHPNmL4vVy3oPw@pryzbyj2023
---
 src/backend/commands/repack_worker.c          |  1 -
 .../test_plan_advice/t/001_replan_regress.pl  |  1 +
 src/test/regress/expected/cluster.out         | 20 +++++++++++++++++--
 src/test/regress/sql/cluster.sql              | 11 ++++++++--
 4 files changed, 28 insertions(+), 5 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index b17edd771e2..e4a4860805b 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -214,7 +214,6 @@ repack_setup_logical_decoding(Oid relid)
 	/*
 	 * Make sure we can use logical decoding.
 	 */
-	CheckSlotPermissions();
 	CheckLogicalDecodingRequirements(true);
 
 	/*
diff --git a/src/test/modules/test_plan_advice/t/001_replan_regress.pl b/src/test/modules/test_plan_advice/t/001_replan_regress.pl
index 38ffa4d11ae..452b179a665 100644
--- a/src/test/modules/test_plan_advice/t/001_replan_regress.pl
+++ b/src/test/modules/test_plan_advice/t/001_replan_regress.pl
@@ -18,6 +18,7 @@ $node->init();
 # Set up our desired configuration.
 $node->append_conf('postgresql.conf', <<EOM);
 shared_preload_libraries='test_plan_advice'
+wal_level=replica
 pg_plan_advice.always_explain_supplied_advice=false
 pg_plan_advice.feedback_warnings=true
 EOM
diff --git a/src/test/regress/expected/cluster.out b/src/test/regress/expected/cluster.out
index 6127b215a86..e17bc91fae1 100644
--- a/src/test/regress/expected/cluster.out
+++ b/src/test/regress/expected/cluster.out
@@ -543,15 +543,17 @@ ERROR:  REPACK (CONCURRENTLY) is not supported for partitioned tables
 HINT:  Consider running the command on individual partitions.
 DROP TABLE clstrpart;
 -- Ownership of partitions is checked
-CREATE TABLE ptnowner(i int unique) PARTITION BY LIST (i);
+CREATE TABLE ptnowner(i int unique not null) PARTITION BY LIST (i);
 CREATE INDEX ptnowner_i_idx ON ptnowner(i);
 CREATE TABLE ptnowner1 PARTITION OF ptnowner FOR VALUES IN (1);
-CREATE ROLE regress_ptnowner;
+CREATE ROLE regress_ptnowner LOGIN;
 CREATE TABLE ptnowner2 PARTITION OF ptnowner FOR VALUES IN (2);
 ALTER TABLE ptnowner1 OWNER TO regress_ptnowner;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
 ERROR:  permission denied for table ptnowner
+ALTER TABLE ptnowner1 REPLICA IDENTITY USING INDEX ptnowner1_i_key;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
 ALTER TABLE ptnowner OWNER TO regress_ptnowner;
 CREATE TEMP TABLE ptnowner_oldnodes AS
@@ -560,6 +562,11 @@ CREATE TEMP TABLE ptnowner_oldnodes AS
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
 WARNING:  permission denied to execute CLUSTER on "ptnowner2", skipping it
+-- still can't repack without a replica identity
+ALTER TABLE ptnowner1 REPLICA IDENTITY DEFAULT;
+REPACK (CONCURRENTLY) ptnowner1;
+ERROR:  cannot process relation "ptnowner1"
+HINT:  Relation "ptnowner1" has no identity index.
 RESET SESSION AUTHORIZATION;
 SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
   JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
@@ -570,6 +577,15 @@ SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
  ptnowner2 | t
 (3 rows)
 
+SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
+  JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
+  relname  | ?column? 
+-----------+----------
+ ptnowner  | t
+ ptnowner1 | f
+ ptnowner2 | t
+(3 rows)
+
 DROP TABLE ptnowner;
 DROP ROLE regress_ptnowner;
 -- Test CLUSTER with external tuplesorting
diff --git a/src/test/regress/sql/cluster.sql b/src/test/regress/sql/cluster.sql
index d14063a9683..1f471a8821a 100644
--- a/src/test/regress/sql/cluster.sql
+++ b/src/test/regress/sql/cluster.sql
@@ -254,14 +254,16 @@ REPACK (CONCURRENTLY) clstrpart;
 DROP TABLE clstrpart;
 
 -- Ownership of partitions is checked
-CREATE TABLE ptnowner(i int unique) PARTITION BY LIST (i);
+CREATE TABLE ptnowner(i int unique not null) PARTITION BY LIST (i);
 CREATE INDEX ptnowner_i_idx ON ptnowner(i);
 CREATE TABLE ptnowner1 PARTITION OF ptnowner FOR VALUES IN (1);
-CREATE ROLE regress_ptnowner;
+CREATE ROLE regress_ptnowner LOGIN;
 CREATE TABLE ptnowner2 PARTITION OF ptnowner FOR VALUES IN (2);
 ALTER TABLE ptnowner1 OWNER TO regress_ptnowner;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
+ALTER TABLE ptnowner1 REPLICA IDENTITY USING INDEX ptnowner1_i_key;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
 ALTER TABLE ptnowner OWNER TO regress_ptnowner;
 CREATE TEMP TABLE ptnowner_oldnodes AS
@@ -269,7 +271,12 @@ CREATE TEMP TABLE ptnowner_oldnodes AS
   JOIN pg_class AS c ON c.oid=tree.relid;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
+-- still can't repack without a replica identity
+ALTER TABLE ptnowner1 REPLICA IDENTITY DEFAULT;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
+SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
+  JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
 SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
   JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
 DROP TABLE ptnowner;
-- 
2.47.3


--m6zy3l65ushq557m
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-REPACK-do-not-require-LOGIN-privileges.patch"



^ permalink  raw  reply  [nested|flat] 58+ messages in thread

* [PATCH 1/2] REPACK: do not require the user to have REPLICATION
@ 2026-04-20 09:38  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 58+ messages in thread

From: Álvaro Herrera @ 2026-04-20 09:38 UTC (permalink / raw)

Although REPACK (CONCURRENTLY) uses replication slots, there is no
concern that the slot will leak data of other users, because the
MAINTAIN privilege on the table is required anyway; requiring
REPLICATION is user-unfriendly without providing any actual protection.

A related aspect is that the REPLICATION attribute is not needed to
prevent REPACK from stealing slots from logical replication, since
commit e76d8c749c31 made REPACK use a separate pool of replication
slots.

Because there are now successful concurrent repack runs in the
regression tests, we're forced to run test_plan_advice under
wal_level=replica.

Author: Antonin Houska <[email protected]>
Reported-by: Justin Pryzby <[email protected]>
Reviewed-by: Chao Li <[email protected]>
Discussion: https://postgr.es/m/aeJHPNmL4vVy3oPw@pryzbyj2023
---
 src/backend/commands/repack_worker.c          |  1 -
 .../test_plan_advice/t/001_replan_regress.pl  |  1 +
 src/test/regress/expected/cluster.out         | 20 +++++++++++++++++--
 src/test/regress/sql/cluster.sql              | 11 ++++++++--
 4 files changed, 28 insertions(+), 5 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index b17edd771e2..e4a4860805b 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -214,7 +214,6 @@ repack_setup_logical_decoding(Oid relid)
 	/*
 	 * Make sure we can use logical decoding.
 	 */
-	CheckSlotPermissions();
 	CheckLogicalDecodingRequirements(true);
 
 	/*
diff --git a/src/test/modules/test_plan_advice/t/001_replan_regress.pl b/src/test/modules/test_plan_advice/t/001_replan_regress.pl
index 38ffa4d11ae..452b179a665 100644
--- a/src/test/modules/test_plan_advice/t/001_replan_regress.pl
+++ b/src/test/modules/test_plan_advice/t/001_replan_regress.pl
@@ -18,6 +18,7 @@ $node->init();
 # Set up our desired configuration.
 $node->append_conf('postgresql.conf', <<EOM);
 shared_preload_libraries='test_plan_advice'
+wal_level=replica
 pg_plan_advice.always_explain_supplied_advice=false
 pg_plan_advice.feedback_warnings=true
 EOM
diff --git a/src/test/regress/expected/cluster.out b/src/test/regress/expected/cluster.out
index 6127b215a86..e17bc91fae1 100644
--- a/src/test/regress/expected/cluster.out
+++ b/src/test/regress/expected/cluster.out
@@ -543,15 +543,17 @@ ERROR:  REPACK (CONCURRENTLY) is not supported for partitioned tables
 HINT:  Consider running the command on individual partitions.
 DROP TABLE clstrpart;
 -- Ownership of partitions is checked
-CREATE TABLE ptnowner(i int unique) PARTITION BY LIST (i);
+CREATE TABLE ptnowner(i int unique not null) PARTITION BY LIST (i);
 CREATE INDEX ptnowner_i_idx ON ptnowner(i);
 CREATE TABLE ptnowner1 PARTITION OF ptnowner FOR VALUES IN (1);
-CREATE ROLE regress_ptnowner;
+CREATE ROLE regress_ptnowner LOGIN;
 CREATE TABLE ptnowner2 PARTITION OF ptnowner FOR VALUES IN (2);
 ALTER TABLE ptnowner1 OWNER TO regress_ptnowner;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
 ERROR:  permission denied for table ptnowner
+ALTER TABLE ptnowner1 REPLICA IDENTITY USING INDEX ptnowner1_i_key;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
 ALTER TABLE ptnowner OWNER TO regress_ptnowner;
 CREATE TEMP TABLE ptnowner_oldnodes AS
@@ -560,6 +562,11 @@ CREATE TEMP TABLE ptnowner_oldnodes AS
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
 WARNING:  permission denied to execute CLUSTER on "ptnowner2", skipping it
+-- still can't repack without a replica identity
+ALTER TABLE ptnowner1 REPLICA IDENTITY DEFAULT;
+REPACK (CONCURRENTLY) ptnowner1;
+ERROR:  cannot process relation "ptnowner1"
+HINT:  Relation "ptnowner1" has no identity index.
 RESET SESSION AUTHORIZATION;
 SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
   JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
@@ -570,6 +577,15 @@ SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
  ptnowner2 | t
 (3 rows)
 
+SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
+  JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
+  relname  | ?column? 
+-----------+----------
+ ptnowner  | t
+ ptnowner1 | f
+ ptnowner2 | t
+(3 rows)
+
 DROP TABLE ptnowner;
 DROP ROLE regress_ptnowner;
 -- Test CLUSTER with external tuplesorting
diff --git a/src/test/regress/sql/cluster.sql b/src/test/regress/sql/cluster.sql
index d14063a9683..1f471a8821a 100644
--- a/src/test/regress/sql/cluster.sql
+++ b/src/test/regress/sql/cluster.sql
@@ -254,14 +254,16 @@ REPACK (CONCURRENTLY) clstrpart;
 DROP TABLE clstrpart;
 
 -- Ownership of partitions is checked
-CREATE TABLE ptnowner(i int unique) PARTITION BY LIST (i);
+CREATE TABLE ptnowner(i int unique not null) PARTITION BY LIST (i);
 CREATE INDEX ptnowner_i_idx ON ptnowner(i);
 CREATE TABLE ptnowner1 PARTITION OF ptnowner FOR VALUES IN (1);
-CREATE ROLE regress_ptnowner;
+CREATE ROLE regress_ptnowner LOGIN;
 CREATE TABLE ptnowner2 PARTITION OF ptnowner FOR VALUES IN (2);
 ALTER TABLE ptnowner1 OWNER TO regress_ptnowner;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
+ALTER TABLE ptnowner1 REPLICA IDENTITY USING INDEX ptnowner1_i_key;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
 ALTER TABLE ptnowner OWNER TO regress_ptnowner;
 CREATE TEMP TABLE ptnowner_oldnodes AS
@@ -269,7 +271,12 @@ CREATE TEMP TABLE ptnowner_oldnodes AS
   JOIN pg_class AS c ON c.oid=tree.relid;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
+-- still can't repack without a replica identity
+ALTER TABLE ptnowner1 REPLICA IDENTITY DEFAULT;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
+SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
+  JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
 SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
   JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
 DROP TABLE ptnowner;
-- 
2.47.3


--m6zy3l65ushq557m
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-REPACK-do-not-require-LOGIN-privileges.patch"



^ permalink  raw  reply  [nested|flat] 58+ messages in thread

* [PATCH 1/2] REPACK: do not require the user to have REPLICATION
@ 2026-04-20 09:38  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 58+ messages in thread

From: Álvaro Herrera @ 2026-04-20 09:38 UTC (permalink / raw)

Although REPACK (CONCURRENTLY) uses replication slots, there is no
concern that the slot will leak data of other users, because the
MAINTAIN privilege on the table is required anyway; requiring
REPLICATION is user-unfriendly without providing any actual protection.

A related aspect is that the REPLICATION attribute is not needed to
prevent REPACK from stealing slots from logical replication, since
commit e76d8c749c31 made REPACK use a separate pool of replication
slots.

Because there are now successful concurrent repack runs in the
regression tests, we're forced to run test_plan_advice under
wal_level=replica.

Author: Antonin Houska <[email protected]>
Reported-by: Justin Pryzby <[email protected]>
Reviewed-by: Chao Li <[email protected]>
Discussion: https://postgr.es/m/aeJHPNmL4vVy3oPw@pryzbyj2023
---
 src/backend/commands/repack_worker.c          |  1 -
 .../test_plan_advice/t/001_replan_regress.pl  |  1 +
 src/test/regress/expected/cluster.out         | 20 +++++++++++++++++--
 src/test/regress/sql/cluster.sql              | 11 ++++++++--
 4 files changed, 28 insertions(+), 5 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index b17edd771e2..e4a4860805b 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -214,7 +214,6 @@ repack_setup_logical_decoding(Oid relid)
 	/*
 	 * Make sure we can use logical decoding.
 	 */
-	CheckSlotPermissions();
 	CheckLogicalDecodingRequirements(true);
 
 	/*
diff --git a/src/test/modules/test_plan_advice/t/001_replan_regress.pl b/src/test/modules/test_plan_advice/t/001_replan_regress.pl
index 38ffa4d11ae..452b179a665 100644
--- a/src/test/modules/test_plan_advice/t/001_replan_regress.pl
+++ b/src/test/modules/test_plan_advice/t/001_replan_regress.pl
@@ -18,6 +18,7 @@ $node->init();
 # Set up our desired configuration.
 $node->append_conf('postgresql.conf', <<EOM);
 shared_preload_libraries='test_plan_advice'
+wal_level=replica
 pg_plan_advice.always_explain_supplied_advice=false
 pg_plan_advice.feedback_warnings=true
 EOM
diff --git a/src/test/regress/expected/cluster.out b/src/test/regress/expected/cluster.out
index 6127b215a86..e17bc91fae1 100644
--- a/src/test/regress/expected/cluster.out
+++ b/src/test/regress/expected/cluster.out
@@ -543,15 +543,17 @@ ERROR:  REPACK (CONCURRENTLY) is not supported for partitioned tables
 HINT:  Consider running the command on individual partitions.
 DROP TABLE clstrpart;
 -- Ownership of partitions is checked
-CREATE TABLE ptnowner(i int unique) PARTITION BY LIST (i);
+CREATE TABLE ptnowner(i int unique not null) PARTITION BY LIST (i);
 CREATE INDEX ptnowner_i_idx ON ptnowner(i);
 CREATE TABLE ptnowner1 PARTITION OF ptnowner FOR VALUES IN (1);
-CREATE ROLE regress_ptnowner;
+CREATE ROLE regress_ptnowner LOGIN;
 CREATE TABLE ptnowner2 PARTITION OF ptnowner FOR VALUES IN (2);
 ALTER TABLE ptnowner1 OWNER TO regress_ptnowner;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
 ERROR:  permission denied for table ptnowner
+ALTER TABLE ptnowner1 REPLICA IDENTITY USING INDEX ptnowner1_i_key;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
 ALTER TABLE ptnowner OWNER TO regress_ptnowner;
 CREATE TEMP TABLE ptnowner_oldnodes AS
@@ -560,6 +562,11 @@ CREATE TEMP TABLE ptnowner_oldnodes AS
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
 WARNING:  permission denied to execute CLUSTER on "ptnowner2", skipping it
+-- still can't repack without a replica identity
+ALTER TABLE ptnowner1 REPLICA IDENTITY DEFAULT;
+REPACK (CONCURRENTLY) ptnowner1;
+ERROR:  cannot process relation "ptnowner1"
+HINT:  Relation "ptnowner1" has no identity index.
 RESET SESSION AUTHORIZATION;
 SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
   JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
@@ -570,6 +577,15 @@ SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
  ptnowner2 | t
 (3 rows)
 
+SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
+  JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
+  relname  | ?column? 
+-----------+----------
+ ptnowner  | t
+ ptnowner1 | f
+ ptnowner2 | t
+(3 rows)
+
 DROP TABLE ptnowner;
 DROP ROLE regress_ptnowner;
 -- Test CLUSTER with external tuplesorting
diff --git a/src/test/regress/sql/cluster.sql b/src/test/regress/sql/cluster.sql
index d14063a9683..1f471a8821a 100644
--- a/src/test/regress/sql/cluster.sql
+++ b/src/test/regress/sql/cluster.sql
@@ -254,14 +254,16 @@ REPACK (CONCURRENTLY) clstrpart;
 DROP TABLE clstrpart;
 
 -- Ownership of partitions is checked
-CREATE TABLE ptnowner(i int unique) PARTITION BY LIST (i);
+CREATE TABLE ptnowner(i int unique not null) PARTITION BY LIST (i);
 CREATE INDEX ptnowner_i_idx ON ptnowner(i);
 CREATE TABLE ptnowner1 PARTITION OF ptnowner FOR VALUES IN (1);
-CREATE ROLE regress_ptnowner;
+CREATE ROLE regress_ptnowner LOGIN;
 CREATE TABLE ptnowner2 PARTITION OF ptnowner FOR VALUES IN (2);
 ALTER TABLE ptnowner1 OWNER TO regress_ptnowner;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
+ALTER TABLE ptnowner1 REPLICA IDENTITY USING INDEX ptnowner1_i_key;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
 ALTER TABLE ptnowner OWNER TO regress_ptnowner;
 CREATE TEMP TABLE ptnowner_oldnodes AS
@@ -269,7 +271,12 @@ CREATE TEMP TABLE ptnowner_oldnodes AS
   JOIN pg_class AS c ON c.oid=tree.relid;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
+-- still can't repack without a replica identity
+ALTER TABLE ptnowner1 REPLICA IDENTITY DEFAULT;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
+SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
+  JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
 SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
   JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
 DROP TABLE ptnowner;
-- 
2.47.3


--m6zy3l65ushq557m
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-REPACK-do-not-require-LOGIN-privileges.patch"



^ permalink  raw  reply  [nested|flat] 58+ messages in thread

* [PATCH 1/2] REPACK: do not require the user to have REPLICATION
@ 2026-04-20 09:38  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 58+ messages in thread

From: Álvaro Herrera @ 2026-04-20 09:38 UTC (permalink / raw)

Although REPACK (CONCURRENTLY) uses replication slots, there is no
concern that the slot will leak data of other users, because the
MAINTAIN privilege on the table is required anyway; requiring
REPLICATION is user-unfriendly without providing any actual protection.

A related aspect is that the REPLICATION attribute is not needed to
prevent REPACK from stealing slots from logical replication, since
commit e76d8c749c31 made REPACK use a separate pool of replication
slots.

Because there are now successful concurrent repack runs in the
regression tests, we're forced to run test_plan_advice under
wal_level=replica.

Author: Antonin Houska <[email protected]>
Reported-by: Justin Pryzby <[email protected]>
Reviewed-by: Chao Li <[email protected]>
Discussion: https://postgr.es/m/aeJHPNmL4vVy3oPw@pryzbyj2023
---
 src/backend/commands/repack_worker.c          |  1 -
 .../test_plan_advice/t/001_replan_regress.pl  |  1 +
 src/test/regress/expected/cluster.out         | 20 +++++++++++++++++--
 src/test/regress/sql/cluster.sql              | 11 ++++++++--
 4 files changed, 28 insertions(+), 5 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index b17edd771e2..e4a4860805b 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -214,7 +214,6 @@ repack_setup_logical_decoding(Oid relid)
 	/*
 	 * Make sure we can use logical decoding.
 	 */
-	CheckSlotPermissions();
 	CheckLogicalDecodingRequirements(true);
 
 	/*
diff --git a/src/test/modules/test_plan_advice/t/001_replan_regress.pl b/src/test/modules/test_plan_advice/t/001_replan_regress.pl
index 38ffa4d11ae..452b179a665 100644
--- a/src/test/modules/test_plan_advice/t/001_replan_regress.pl
+++ b/src/test/modules/test_plan_advice/t/001_replan_regress.pl
@@ -18,6 +18,7 @@ $node->init();
 # Set up our desired configuration.
 $node->append_conf('postgresql.conf', <<EOM);
 shared_preload_libraries='test_plan_advice'
+wal_level=replica
 pg_plan_advice.always_explain_supplied_advice=false
 pg_plan_advice.feedback_warnings=true
 EOM
diff --git a/src/test/regress/expected/cluster.out b/src/test/regress/expected/cluster.out
index 6127b215a86..e17bc91fae1 100644
--- a/src/test/regress/expected/cluster.out
+++ b/src/test/regress/expected/cluster.out
@@ -543,15 +543,17 @@ ERROR:  REPACK (CONCURRENTLY) is not supported for partitioned tables
 HINT:  Consider running the command on individual partitions.
 DROP TABLE clstrpart;
 -- Ownership of partitions is checked
-CREATE TABLE ptnowner(i int unique) PARTITION BY LIST (i);
+CREATE TABLE ptnowner(i int unique not null) PARTITION BY LIST (i);
 CREATE INDEX ptnowner_i_idx ON ptnowner(i);
 CREATE TABLE ptnowner1 PARTITION OF ptnowner FOR VALUES IN (1);
-CREATE ROLE regress_ptnowner;
+CREATE ROLE regress_ptnowner LOGIN;
 CREATE TABLE ptnowner2 PARTITION OF ptnowner FOR VALUES IN (2);
 ALTER TABLE ptnowner1 OWNER TO regress_ptnowner;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
 ERROR:  permission denied for table ptnowner
+ALTER TABLE ptnowner1 REPLICA IDENTITY USING INDEX ptnowner1_i_key;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
 ALTER TABLE ptnowner OWNER TO regress_ptnowner;
 CREATE TEMP TABLE ptnowner_oldnodes AS
@@ -560,6 +562,11 @@ CREATE TEMP TABLE ptnowner_oldnodes AS
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
 WARNING:  permission denied to execute CLUSTER on "ptnowner2", skipping it
+-- still can't repack without a replica identity
+ALTER TABLE ptnowner1 REPLICA IDENTITY DEFAULT;
+REPACK (CONCURRENTLY) ptnowner1;
+ERROR:  cannot process relation "ptnowner1"
+HINT:  Relation "ptnowner1" has no identity index.
 RESET SESSION AUTHORIZATION;
 SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
   JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
@@ -570,6 +577,15 @@ SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
  ptnowner2 | t
 (3 rows)
 
+SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
+  JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
+  relname  | ?column? 
+-----------+----------
+ ptnowner  | t
+ ptnowner1 | f
+ ptnowner2 | t
+(3 rows)
+
 DROP TABLE ptnowner;
 DROP ROLE regress_ptnowner;
 -- Test CLUSTER with external tuplesorting
diff --git a/src/test/regress/sql/cluster.sql b/src/test/regress/sql/cluster.sql
index d14063a9683..1f471a8821a 100644
--- a/src/test/regress/sql/cluster.sql
+++ b/src/test/regress/sql/cluster.sql
@@ -254,14 +254,16 @@ REPACK (CONCURRENTLY) clstrpart;
 DROP TABLE clstrpart;
 
 -- Ownership of partitions is checked
-CREATE TABLE ptnowner(i int unique) PARTITION BY LIST (i);
+CREATE TABLE ptnowner(i int unique not null) PARTITION BY LIST (i);
 CREATE INDEX ptnowner_i_idx ON ptnowner(i);
 CREATE TABLE ptnowner1 PARTITION OF ptnowner FOR VALUES IN (1);
-CREATE ROLE regress_ptnowner;
+CREATE ROLE regress_ptnowner LOGIN;
 CREATE TABLE ptnowner2 PARTITION OF ptnowner FOR VALUES IN (2);
 ALTER TABLE ptnowner1 OWNER TO regress_ptnowner;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
+ALTER TABLE ptnowner1 REPLICA IDENTITY USING INDEX ptnowner1_i_key;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
 ALTER TABLE ptnowner OWNER TO regress_ptnowner;
 CREATE TEMP TABLE ptnowner_oldnodes AS
@@ -269,7 +271,12 @@ CREATE TEMP TABLE ptnowner_oldnodes AS
   JOIN pg_class AS c ON c.oid=tree.relid;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
+-- still can't repack without a replica identity
+ALTER TABLE ptnowner1 REPLICA IDENTITY DEFAULT;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
+SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
+  JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
 SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
   JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
 DROP TABLE ptnowner;
-- 
2.47.3


--m6zy3l65ushq557m
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-REPACK-do-not-require-LOGIN-privileges.patch"



^ permalink  raw  reply  [nested|flat] 58+ messages in thread

* [PATCH 1/2] REPACK: do not require the user to have REPLICATION
@ 2026-04-20 09:38  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 58+ messages in thread

From: Álvaro Herrera @ 2026-04-20 09:38 UTC (permalink / raw)

Although REPACK (CONCURRENTLY) uses replication slots, there is no
concern that the slot will leak data of other users, because the
MAINTAIN privilege on the table is required anyway; requiring
REPLICATION is user-unfriendly without providing any actual protection.

A related aspect is that the REPLICATION attribute is not needed to
prevent REPACK from stealing slots from logical replication, since
commit e76d8c749c31 made REPACK use a separate pool of replication
slots.

Because there are now successful concurrent repack runs in the
regression tests, we're forced to run test_plan_advice under
wal_level=replica.

Author: Antonin Houska <[email protected]>
Reported-by: Justin Pryzby <[email protected]>
Reviewed-by: Chao Li <[email protected]>
Discussion: https://postgr.es/m/aeJHPNmL4vVy3oPw@pryzbyj2023
---
 src/backend/commands/repack_worker.c          |  1 -
 .../test_plan_advice/t/001_replan_regress.pl  |  1 +
 src/test/regress/expected/cluster.out         | 20 +++++++++++++++++--
 src/test/regress/sql/cluster.sql              | 11 ++++++++--
 4 files changed, 28 insertions(+), 5 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index b17edd771e2..e4a4860805b 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -214,7 +214,6 @@ repack_setup_logical_decoding(Oid relid)
 	/*
 	 * Make sure we can use logical decoding.
 	 */
-	CheckSlotPermissions();
 	CheckLogicalDecodingRequirements(true);
 
 	/*
diff --git a/src/test/modules/test_plan_advice/t/001_replan_regress.pl b/src/test/modules/test_plan_advice/t/001_replan_regress.pl
index 38ffa4d11ae..452b179a665 100644
--- a/src/test/modules/test_plan_advice/t/001_replan_regress.pl
+++ b/src/test/modules/test_plan_advice/t/001_replan_regress.pl
@@ -18,6 +18,7 @@ $node->init();
 # Set up our desired configuration.
 $node->append_conf('postgresql.conf', <<EOM);
 shared_preload_libraries='test_plan_advice'
+wal_level=replica
 pg_plan_advice.always_explain_supplied_advice=false
 pg_plan_advice.feedback_warnings=true
 EOM
diff --git a/src/test/regress/expected/cluster.out b/src/test/regress/expected/cluster.out
index 6127b215a86..e17bc91fae1 100644
--- a/src/test/regress/expected/cluster.out
+++ b/src/test/regress/expected/cluster.out
@@ -543,15 +543,17 @@ ERROR:  REPACK (CONCURRENTLY) is not supported for partitioned tables
 HINT:  Consider running the command on individual partitions.
 DROP TABLE clstrpart;
 -- Ownership of partitions is checked
-CREATE TABLE ptnowner(i int unique) PARTITION BY LIST (i);
+CREATE TABLE ptnowner(i int unique not null) PARTITION BY LIST (i);
 CREATE INDEX ptnowner_i_idx ON ptnowner(i);
 CREATE TABLE ptnowner1 PARTITION OF ptnowner FOR VALUES IN (1);
-CREATE ROLE regress_ptnowner;
+CREATE ROLE regress_ptnowner LOGIN;
 CREATE TABLE ptnowner2 PARTITION OF ptnowner FOR VALUES IN (2);
 ALTER TABLE ptnowner1 OWNER TO regress_ptnowner;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
 ERROR:  permission denied for table ptnowner
+ALTER TABLE ptnowner1 REPLICA IDENTITY USING INDEX ptnowner1_i_key;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
 ALTER TABLE ptnowner OWNER TO regress_ptnowner;
 CREATE TEMP TABLE ptnowner_oldnodes AS
@@ -560,6 +562,11 @@ CREATE TEMP TABLE ptnowner_oldnodes AS
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
 WARNING:  permission denied to execute CLUSTER on "ptnowner2", skipping it
+-- still can't repack without a replica identity
+ALTER TABLE ptnowner1 REPLICA IDENTITY DEFAULT;
+REPACK (CONCURRENTLY) ptnowner1;
+ERROR:  cannot process relation "ptnowner1"
+HINT:  Relation "ptnowner1" has no identity index.
 RESET SESSION AUTHORIZATION;
 SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
   JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
@@ -570,6 +577,15 @@ SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
  ptnowner2 | t
 (3 rows)
 
+SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
+  JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
+  relname  | ?column? 
+-----------+----------
+ ptnowner  | t
+ ptnowner1 | f
+ ptnowner2 | t
+(3 rows)
+
 DROP TABLE ptnowner;
 DROP ROLE regress_ptnowner;
 -- Test CLUSTER with external tuplesorting
diff --git a/src/test/regress/sql/cluster.sql b/src/test/regress/sql/cluster.sql
index d14063a9683..1f471a8821a 100644
--- a/src/test/regress/sql/cluster.sql
+++ b/src/test/regress/sql/cluster.sql
@@ -254,14 +254,16 @@ REPACK (CONCURRENTLY) clstrpart;
 DROP TABLE clstrpart;
 
 -- Ownership of partitions is checked
-CREATE TABLE ptnowner(i int unique) PARTITION BY LIST (i);
+CREATE TABLE ptnowner(i int unique not null) PARTITION BY LIST (i);
 CREATE INDEX ptnowner_i_idx ON ptnowner(i);
 CREATE TABLE ptnowner1 PARTITION OF ptnowner FOR VALUES IN (1);
-CREATE ROLE regress_ptnowner;
+CREATE ROLE regress_ptnowner LOGIN;
 CREATE TABLE ptnowner2 PARTITION OF ptnowner FOR VALUES IN (2);
 ALTER TABLE ptnowner1 OWNER TO regress_ptnowner;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
+ALTER TABLE ptnowner1 REPLICA IDENTITY USING INDEX ptnowner1_i_key;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
 ALTER TABLE ptnowner OWNER TO regress_ptnowner;
 CREATE TEMP TABLE ptnowner_oldnodes AS
@@ -269,7 +271,12 @@ CREATE TEMP TABLE ptnowner_oldnodes AS
   JOIN pg_class AS c ON c.oid=tree.relid;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
+-- still can't repack without a replica identity
+ALTER TABLE ptnowner1 REPLICA IDENTITY DEFAULT;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
+SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
+  JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
 SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
   JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
 DROP TABLE ptnowner;
-- 
2.47.3


--m6zy3l65ushq557m
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-REPACK-do-not-require-LOGIN-privileges.patch"



^ permalink  raw  reply  [nested|flat] 58+ messages in thread

* [PATCH 1/2] REPACK: do not require the user to have REPLICATION
@ 2026-04-20 09:38  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 58+ messages in thread

From: Álvaro Herrera @ 2026-04-20 09:38 UTC (permalink / raw)

Although REPACK (CONCURRENTLY) uses replication slots, there is no
concern that the slot will leak data of other users, because the
MAINTAIN privilege on the table is required anyway; requiring
REPLICATION is user-unfriendly without providing any actual protection.

A related aspect is that the REPLICATION attribute is not needed to
prevent REPACK from stealing slots from logical replication, since
commit e76d8c749c31 made REPACK use a separate pool of replication
slots.

Because there are now successful concurrent repack runs in the
regression tests, we're forced to run test_plan_advice under
wal_level=replica.

Author: Antonin Houska <[email protected]>
Reported-by: Justin Pryzby <[email protected]>
Reviewed-by: Chao Li <[email protected]>
Discussion: https://postgr.es/m/aeJHPNmL4vVy3oPw@pryzbyj2023
---
 src/backend/commands/repack_worker.c          |  1 -
 .../test_plan_advice/t/001_replan_regress.pl  |  1 +
 src/test/regress/expected/cluster.out         | 20 +++++++++++++++++--
 src/test/regress/sql/cluster.sql              | 11 ++++++++--
 4 files changed, 28 insertions(+), 5 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index b17edd771e2..e4a4860805b 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -214,7 +214,6 @@ repack_setup_logical_decoding(Oid relid)
 	/*
 	 * Make sure we can use logical decoding.
 	 */
-	CheckSlotPermissions();
 	CheckLogicalDecodingRequirements(true);
 
 	/*
diff --git a/src/test/modules/test_plan_advice/t/001_replan_regress.pl b/src/test/modules/test_plan_advice/t/001_replan_regress.pl
index 38ffa4d11ae..452b179a665 100644
--- a/src/test/modules/test_plan_advice/t/001_replan_regress.pl
+++ b/src/test/modules/test_plan_advice/t/001_replan_regress.pl
@@ -18,6 +18,7 @@ $node->init();
 # Set up our desired configuration.
 $node->append_conf('postgresql.conf', <<EOM);
 shared_preload_libraries='test_plan_advice'
+wal_level=replica
 pg_plan_advice.always_explain_supplied_advice=false
 pg_plan_advice.feedback_warnings=true
 EOM
diff --git a/src/test/regress/expected/cluster.out b/src/test/regress/expected/cluster.out
index 6127b215a86..e17bc91fae1 100644
--- a/src/test/regress/expected/cluster.out
+++ b/src/test/regress/expected/cluster.out
@@ -543,15 +543,17 @@ ERROR:  REPACK (CONCURRENTLY) is not supported for partitioned tables
 HINT:  Consider running the command on individual partitions.
 DROP TABLE clstrpart;
 -- Ownership of partitions is checked
-CREATE TABLE ptnowner(i int unique) PARTITION BY LIST (i);
+CREATE TABLE ptnowner(i int unique not null) PARTITION BY LIST (i);
 CREATE INDEX ptnowner_i_idx ON ptnowner(i);
 CREATE TABLE ptnowner1 PARTITION OF ptnowner FOR VALUES IN (1);
-CREATE ROLE regress_ptnowner;
+CREATE ROLE regress_ptnowner LOGIN;
 CREATE TABLE ptnowner2 PARTITION OF ptnowner FOR VALUES IN (2);
 ALTER TABLE ptnowner1 OWNER TO regress_ptnowner;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
 ERROR:  permission denied for table ptnowner
+ALTER TABLE ptnowner1 REPLICA IDENTITY USING INDEX ptnowner1_i_key;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
 ALTER TABLE ptnowner OWNER TO regress_ptnowner;
 CREATE TEMP TABLE ptnowner_oldnodes AS
@@ -560,6 +562,11 @@ CREATE TEMP TABLE ptnowner_oldnodes AS
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
 WARNING:  permission denied to execute CLUSTER on "ptnowner2", skipping it
+-- still can't repack without a replica identity
+ALTER TABLE ptnowner1 REPLICA IDENTITY DEFAULT;
+REPACK (CONCURRENTLY) ptnowner1;
+ERROR:  cannot process relation "ptnowner1"
+HINT:  Relation "ptnowner1" has no identity index.
 RESET SESSION AUTHORIZATION;
 SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
   JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
@@ -570,6 +577,15 @@ SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
  ptnowner2 | t
 (3 rows)
 
+SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
+  JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
+  relname  | ?column? 
+-----------+----------
+ ptnowner  | t
+ ptnowner1 | f
+ ptnowner2 | t
+(3 rows)
+
 DROP TABLE ptnowner;
 DROP ROLE regress_ptnowner;
 -- Test CLUSTER with external tuplesorting
diff --git a/src/test/regress/sql/cluster.sql b/src/test/regress/sql/cluster.sql
index d14063a9683..1f471a8821a 100644
--- a/src/test/regress/sql/cluster.sql
+++ b/src/test/regress/sql/cluster.sql
@@ -254,14 +254,16 @@ REPACK (CONCURRENTLY) clstrpart;
 DROP TABLE clstrpart;
 
 -- Ownership of partitions is checked
-CREATE TABLE ptnowner(i int unique) PARTITION BY LIST (i);
+CREATE TABLE ptnowner(i int unique not null) PARTITION BY LIST (i);
 CREATE INDEX ptnowner_i_idx ON ptnowner(i);
 CREATE TABLE ptnowner1 PARTITION OF ptnowner FOR VALUES IN (1);
-CREATE ROLE regress_ptnowner;
+CREATE ROLE regress_ptnowner LOGIN;
 CREATE TABLE ptnowner2 PARTITION OF ptnowner FOR VALUES IN (2);
 ALTER TABLE ptnowner1 OWNER TO regress_ptnowner;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
+ALTER TABLE ptnowner1 REPLICA IDENTITY USING INDEX ptnowner1_i_key;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
 ALTER TABLE ptnowner OWNER TO regress_ptnowner;
 CREATE TEMP TABLE ptnowner_oldnodes AS
@@ -269,7 +271,12 @@ CREATE TEMP TABLE ptnowner_oldnodes AS
   JOIN pg_class AS c ON c.oid=tree.relid;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
+-- still can't repack without a replica identity
+ALTER TABLE ptnowner1 REPLICA IDENTITY DEFAULT;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
+SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
+  JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
 SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
   JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
 DROP TABLE ptnowner;
-- 
2.47.3


--m6zy3l65ushq557m
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-REPACK-do-not-require-LOGIN-privileges.patch"



^ permalink  raw  reply  [nested|flat] 58+ messages in thread

* [PATCH 1/2] REPACK: do not require the user to have REPLICATION
@ 2026-04-20 09:38  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 58+ messages in thread

From: Álvaro Herrera @ 2026-04-20 09:38 UTC (permalink / raw)

Although REPACK (CONCURRENTLY) uses replication slots, there is no
concern that the slot will leak data of other users, because the
MAINTAIN privilege on the table is required anyway; requiring
REPLICATION is user-unfriendly without providing any actual protection.

A related aspect is that the REPLICATION attribute is not needed to
prevent REPACK from stealing slots from logical replication, since
commit e76d8c749c31 made REPACK use a separate pool of replication
slots.

Because there are now successful concurrent repack runs in the
regression tests, we're forced to run test_plan_advice under
wal_level=replica.

Author: Antonin Houska <[email protected]>
Reported-by: Justin Pryzby <[email protected]>
Reviewed-by: Chao Li <[email protected]>
Discussion: https://postgr.es/m/aeJHPNmL4vVy3oPw@pryzbyj2023
---
 src/backend/commands/repack_worker.c          |  1 -
 .../test_plan_advice/t/001_replan_regress.pl  |  1 +
 src/test/regress/expected/cluster.out         | 20 +++++++++++++++++--
 src/test/regress/sql/cluster.sql              | 11 ++++++++--
 4 files changed, 28 insertions(+), 5 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index b17edd771e2..e4a4860805b 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -214,7 +214,6 @@ repack_setup_logical_decoding(Oid relid)
 	/*
 	 * Make sure we can use logical decoding.
 	 */
-	CheckSlotPermissions();
 	CheckLogicalDecodingRequirements(true);
 
 	/*
diff --git a/src/test/modules/test_plan_advice/t/001_replan_regress.pl b/src/test/modules/test_plan_advice/t/001_replan_regress.pl
index 38ffa4d11ae..452b179a665 100644
--- a/src/test/modules/test_plan_advice/t/001_replan_regress.pl
+++ b/src/test/modules/test_plan_advice/t/001_replan_regress.pl
@@ -18,6 +18,7 @@ $node->init();
 # Set up our desired configuration.
 $node->append_conf('postgresql.conf', <<EOM);
 shared_preload_libraries='test_plan_advice'
+wal_level=replica
 pg_plan_advice.always_explain_supplied_advice=false
 pg_plan_advice.feedback_warnings=true
 EOM
diff --git a/src/test/regress/expected/cluster.out b/src/test/regress/expected/cluster.out
index 6127b215a86..e17bc91fae1 100644
--- a/src/test/regress/expected/cluster.out
+++ b/src/test/regress/expected/cluster.out
@@ -543,15 +543,17 @@ ERROR:  REPACK (CONCURRENTLY) is not supported for partitioned tables
 HINT:  Consider running the command on individual partitions.
 DROP TABLE clstrpart;
 -- Ownership of partitions is checked
-CREATE TABLE ptnowner(i int unique) PARTITION BY LIST (i);
+CREATE TABLE ptnowner(i int unique not null) PARTITION BY LIST (i);
 CREATE INDEX ptnowner_i_idx ON ptnowner(i);
 CREATE TABLE ptnowner1 PARTITION OF ptnowner FOR VALUES IN (1);
-CREATE ROLE regress_ptnowner;
+CREATE ROLE regress_ptnowner LOGIN;
 CREATE TABLE ptnowner2 PARTITION OF ptnowner FOR VALUES IN (2);
 ALTER TABLE ptnowner1 OWNER TO regress_ptnowner;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
 ERROR:  permission denied for table ptnowner
+ALTER TABLE ptnowner1 REPLICA IDENTITY USING INDEX ptnowner1_i_key;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
 ALTER TABLE ptnowner OWNER TO regress_ptnowner;
 CREATE TEMP TABLE ptnowner_oldnodes AS
@@ -560,6 +562,11 @@ CREATE TEMP TABLE ptnowner_oldnodes AS
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
 WARNING:  permission denied to execute CLUSTER on "ptnowner2", skipping it
+-- still can't repack without a replica identity
+ALTER TABLE ptnowner1 REPLICA IDENTITY DEFAULT;
+REPACK (CONCURRENTLY) ptnowner1;
+ERROR:  cannot process relation "ptnowner1"
+HINT:  Relation "ptnowner1" has no identity index.
 RESET SESSION AUTHORIZATION;
 SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
   JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
@@ -570,6 +577,15 @@ SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
  ptnowner2 | t
 (3 rows)
 
+SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
+  JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
+  relname  | ?column? 
+-----------+----------
+ ptnowner  | t
+ ptnowner1 | f
+ ptnowner2 | t
+(3 rows)
+
 DROP TABLE ptnowner;
 DROP ROLE regress_ptnowner;
 -- Test CLUSTER with external tuplesorting
diff --git a/src/test/regress/sql/cluster.sql b/src/test/regress/sql/cluster.sql
index d14063a9683..1f471a8821a 100644
--- a/src/test/regress/sql/cluster.sql
+++ b/src/test/regress/sql/cluster.sql
@@ -254,14 +254,16 @@ REPACK (CONCURRENTLY) clstrpart;
 DROP TABLE clstrpart;
 
 -- Ownership of partitions is checked
-CREATE TABLE ptnowner(i int unique) PARTITION BY LIST (i);
+CREATE TABLE ptnowner(i int unique not null) PARTITION BY LIST (i);
 CREATE INDEX ptnowner_i_idx ON ptnowner(i);
 CREATE TABLE ptnowner1 PARTITION OF ptnowner FOR VALUES IN (1);
-CREATE ROLE regress_ptnowner;
+CREATE ROLE regress_ptnowner LOGIN;
 CREATE TABLE ptnowner2 PARTITION OF ptnowner FOR VALUES IN (2);
 ALTER TABLE ptnowner1 OWNER TO regress_ptnowner;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
+ALTER TABLE ptnowner1 REPLICA IDENTITY USING INDEX ptnowner1_i_key;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
 ALTER TABLE ptnowner OWNER TO regress_ptnowner;
 CREATE TEMP TABLE ptnowner_oldnodes AS
@@ -269,7 +271,12 @@ CREATE TEMP TABLE ptnowner_oldnodes AS
   JOIN pg_class AS c ON c.oid=tree.relid;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
+-- still can't repack without a replica identity
+ALTER TABLE ptnowner1 REPLICA IDENTITY DEFAULT;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
+SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
+  JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
 SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
   JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
 DROP TABLE ptnowner;
-- 
2.47.3


--m6zy3l65ushq557m
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-REPACK-do-not-require-LOGIN-privileges.patch"



^ permalink  raw  reply  [nested|flat] 58+ messages in thread

* [PATCH 1/2] REPACK: do not require the user to have REPLICATION
@ 2026-04-20 09:38  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 58+ messages in thread

From: Álvaro Herrera @ 2026-04-20 09:38 UTC (permalink / raw)

Although REPACK (CONCURRENTLY) uses replication slots, there is no
concern that the slot will leak data of other users, because the
MAINTAIN privilege on the table is required anyway; requiring
REPLICATION is user-unfriendly without providing any actual protection.

A related aspect is that the REPLICATION attribute is not needed to
prevent REPACK from stealing slots from logical replication, since
commit e76d8c749c31 made REPACK use a separate pool of replication
slots.

Because there are now successful concurrent repack runs in the
regression tests, we're forced to run test_plan_advice under
wal_level=replica.

Author: Antonin Houska <[email protected]>
Reported-by: Justin Pryzby <[email protected]>
Reviewed-by: Chao Li <[email protected]>
Discussion: https://postgr.es/m/aeJHPNmL4vVy3oPw@pryzbyj2023
---
 src/backend/commands/repack_worker.c          |  1 -
 .../test_plan_advice/t/001_replan_regress.pl  |  1 +
 src/test/regress/expected/cluster.out         | 20 +++++++++++++++++--
 src/test/regress/sql/cluster.sql              | 11 ++++++++--
 4 files changed, 28 insertions(+), 5 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index b17edd771e2..e4a4860805b 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -214,7 +214,6 @@ repack_setup_logical_decoding(Oid relid)
 	/*
 	 * Make sure we can use logical decoding.
 	 */
-	CheckSlotPermissions();
 	CheckLogicalDecodingRequirements(true);
 
 	/*
diff --git a/src/test/modules/test_plan_advice/t/001_replan_regress.pl b/src/test/modules/test_plan_advice/t/001_replan_regress.pl
index 38ffa4d11ae..452b179a665 100644
--- a/src/test/modules/test_plan_advice/t/001_replan_regress.pl
+++ b/src/test/modules/test_plan_advice/t/001_replan_regress.pl
@@ -18,6 +18,7 @@ $node->init();
 # Set up our desired configuration.
 $node->append_conf('postgresql.conf', <<EOM);
 shared_preload_libraries='test_plan_advice'
+wal_level=replica
 pg_plan_advice.always_explain_supplied_advice=false
 pg_plan_advice.feedback_warnings=true
 EOM
diff --git a/src/test/regress/expected/cluster.out b/src/test/regress/expected/cluster.out
index 6127b215a86..e17bc91fae1 100644
--- a/src/test/regress/expected/cluster.out
+++ b/src/test/regress/expected/cluster.out
@@ -543,15 +543,17 @@ ERROR:  REPACK (CONCURRENTLY) is not supported for partitioned tables
 HINT:  Consider running the command on individual partitions.
 DROP TABLE clstrpart;
 -- Ownership of partitions is checked
-CREATE TABLE ptnowner(i int unique) PARTITION BY LIST (i);
+CREATE TABLE ptnowner(i int unique not null) PARTITION BY LIST (i);
 CREATE INDEX ptnowner_i_idx ON ptnowner(i);
 CREATE TABLE ptnowner1 PARTITION OF ptnowner FOR VALUES IN (1);
-CREATE ROLE regress_ptnowner;
+CREATE ROLE regress_ptnowner LOGIN;
 CREATE TABLE ptnowner2 PARTITION OF ptnowner FOR VALUES IN (2);
 ALTER TABLE ptnowner1 OWNER TO regress_ptnowner;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
 ERROR:  permission denied for table ptnowner
+ALTER TABLE ptnowner1 REPLICA IDENTITY USING INDEX ptnowner1_i_key;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
 ALTER TABLE ptnowner OWNER TO regress_ptnowner;
 CREATE TEMP TABLE ptnowner_oldnodes AS
@@ -560,6 +562,11 @@ CREATE TEMP TABLE ptnowner_oldnodes AS
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
 WARNING:  permission denied to execute CLUSTER on "ptnowner2", skipping it
+-- still can't repack without a replica identity
+ALTER TABLE ptnowner1 REPLICA IDENTITY DEFAULT;
+REPACK (CONCURRENTLY) ptnowner1;
+ERROR:  cannot process relation "ptnowner1"
+HINT:  Relation "ptnowner1" has no identity index.
 RESET SESSION AUTHORIZATION;
 SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
   JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
@@ -570,6 +577,15 @@ SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
  ptnowner2 | t
 (3 rows)
 
+SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
+  JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
+  relname  | ?column? 
+-----------+----------
+ ptnowner  | t
+ ptnowner1 | f
+ ptnowner2 | t
+(3 rows)
+
 DROP TABLE ptnowner;
 DROP ROLE regress_ptnowner;
 -- Test CLUSTER with external tuplesorting
diff --git a/src/test/regress/sql/cluster.sql b/src/test/regress/sql/cluster.sql
index d14063a9683..1f471a8821a 100644
--- a/src/test/regress/sql/cluster.sql
+++ b/src/test/regress/sql/cluster.sql
@@ -254,14 +254,16 @@ REPACK (CONCURRENTLY) clstrpart;
 DROP TABLE clstrpart;
 
 -- Ownership of partitions is checked
-CREATE TABLE ptnowner(i int unique) PARTITION BY LIST (i);
+CREATE TABLE ptnowner(i int unique not null) PARTITION BY LIST (i);
 CREATE INDEX ptnowner_i_idx ON ptnowner(i);
 CREATE TABLE ptnowner1 PARTITION OF ptnowner FOR VALUES IN (1);
-CREATE ROLE regress_ptnowner;
+CREATE ROLE regress_ptnowner LOGIN;
 CREATE TABLE ptnowner2 PARTITION OF ptnowner FOR VALUES IN (2);
 ALTER TABLE ptnowner1 OWNER TO regress_ptnowner;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
+ALTER TABLE ptnowner1 REPLICA IDENTITY USING INDEX ptnowner1_i_key;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
 ALTER TABLE ptnowner OWNER TO regress_ptnowner;
 CREATE TEMP TABLE ptnowner_oldnodes AS
@@ -269,7 +271,12 @@ CREATE TEMP TABLE ptnowner_oldnodes AS
   JOIN pg_class AS c ON c.oid=tree.relid;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
+-- still can't repack without a replica identity
+ALTER TABLE ptnowner1 REPLICA IDENTITY DEFAULT;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
+SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
+  JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
 SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
   JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
 DROP TABLE ptnowner;
-- 
2.47.3


--m6zy3l65ushq557m
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-REPACK-do-not-require-LOGIN-privileges.patch"



^ permalink  raw  reply  [nested|flat] 58+ messages in thread

* [PATCH 1/2] REPACK: do not require the user to have REPLICATION
@ 2026-04-20 09:38  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 58+ messages in thread

From: Álvaro Herrera @ 2026-04-20 09:38 UTC (permalink / raw)

Although REPACK (CONCURRENTLY) uses replication slots, there is no
concern that the slot will leak data of other users, because the
MAINTAIN privilege on the table is required anyway; requiring
REPLICATION is user-unfriendly without providing any actual protection.

A related aspect is that the REPLICATION attribute is not needed to
prevent REPACK from stealing slots from logical replication, since
commit e76d8c749c31 made REPACK use a separate pool of replication
slots.

Because there are now successful concurrent repack runs in the
regression tests, we're forced to run test_plan_advice under
wal_level=replica.

Author: Antonin Houska <[email protected]>
Reported-by: Justin Pryzby <[email protected]>
Reviewed-by: Chao Li <[email protected]>
Discussion: https://postgr.es/m/aeJHPNmL4vVy3oPw@pryzbyj2023
---
 src/backend/commands/repack_worker.c          |  1 -
 .../test_plan_advice/t/001_replan_regress.pl  |  1 +
 src/test/regress/expected/cluster.out         | 20 +++++++++++++++++--
 src/test/regress/sql/cluster.sql              | 11 ++++++++--
 4 files changed, 28 insertions(+), 5 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index b17edd771e2..e4a4860805b 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -214,7 +214,6 @@ repack_setup_logical_decoding(Oid relid)
 	/*
 	 * Make sure we can use logical decoding.
 	 */
-	CheckSlotPermissions();
 	CheckLogicalDecodingRequirements(true);
 
 	/*
diff --git a/src/test/modules/test_plan_advice/t/001_replan_regress.pl b/src/test/modules/test_plan_advice/t/001_replan_regress.pl
index 38ffa4d11ae..452b179a665 100644
--- a/src/test/modules/test_plan_advice/t/001_replan_regress.pl
+++ b/src/test/modules/test_plan_advice/t/001_replan_regress.pl
@@ -18,6 +18,7 @@ $node->init();
 # Set up our desired configuration.
 $node->append_conf('postgresql.conf', <<EOM);
 shared_preload_libraries='test_plan_advice'
+wal_level=replica
 pg_plan_advice.always_explain_supplied_advice=false
 pg_plan_advice.feedback_warnings=true
 EOM
diff --git a/src/test/regress/expected/cluster.out b/src/test/regress/expected/cluster.out
index 6127b215a86..e17bc91fae1 100644
--- a/src/test/regress/expected/cluster.out
+++ b/src/test/regress/expected/cluster.out
@@ -543,15 +543,17 @@ ERROR:  REPACK (CONCURRENTLY) is not supported for partitioned tables
 HINT:  Consider running the command on individual partitions.
 DROP TABLE clstrpart;
 -- Ownership of partitions is checked
-CREATE TABLE ptnowner(i int unique) PARTITION BY LIST (i);
+CREATE TABLE ptnowner(i int unique not null) PARTITION BY LIST (i);
 CREATE INDEX ptnowner_i_idx ON ptnowner(i);
 CREATE TABLE ptnowner1 PARTITION OF ptnowner FOR VALUES IN (1);
-CREATE ROLE regress_ptnowner;
+CREATE ROLE regress_ptnowner LOGIN;
 CREATE TABLE ptnowner2 PARTITION OF ptnowner FOR VALUES IN (2);
 ALTER TABLE ptnowner1 OWNER TO regress_ptnowner;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
 ERROR:  permission denied for table ptnowner
+ALTER TABLE ptnowner1 REPLICA IDENTITY USING INDEX ptnowner1_i_key;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
 ALTER TABLE ptnowner OWNER TO regress_ptnowner;
 CREATE TEMP TABLE ptnowner_oldnodes AS
@@ -560,6 +562,11 @@ CREATE TEMP TABLE ptnowner_oldnodes AS
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
 WARNING:  permission denied to execute CLUSTER on "ptnowner2", skipping it
+-- still can't repack without a replica identity
+ALTER TABLE ptnowner1 REPLICA IDENTITY DEFAULT;
+REPACK (CONCURRENTLY) ptnowner1;
+ERROR:  cannot process relation "ptnowner1"
+HINT:  Relation "ptnowner1" has no identity index.
 RESET SESSION AUTHORIZATION;
 SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
   JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
@@ -570,6 +577,15 @@ SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
  ptnowner2 | t
 (3 rows)
 
+SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
+  JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
+  relname  | ?column? 
+-----------+----------
+ ptnowner  | t
+ ptnowner1 | f
+ ptnowner2 | t
+(3 rows)
+
 DROP TABLE ptnowner;
 DROP ROLE regress_ptnowner;
 -- Test CLUSTER with external tuplesorting
diff --git a/src/test/regress/sql/cluster.sql b/src/test/regress/sql/cluster.sql
index d14063a9683..1f471a8821a 100644
--- a/src/test/regress/sql/cluster.sql
+++ b/src/test/regress/sql/cluster.sql
@@ -254,14 +254,16 @@ REPACK (CONCURRENTLY) clstrpart;
 DROP TABLE clstrpart;
 
 -- Ownership of partitions is checked
-CREATE TABLE ptnowner(i int unique) PARTITION BY LIST (i);
+CREATE TABLE ptnowner(i int unique not null) PARTITION BY LIST (i);
 CREATE INDEX ptnowner_i_idx ON ptnowner(i);
 CREATE TABLE ptnowner1 PARTITION OF ptnowner FOR VALUES IN (1);
-CREATE ROLE regress_ptnowner;
+CREATE ROLE regress_ptnowner LOGIN;
 CREATE TABLE ptnowner2 PARTITION OF ptnowner FOR VALUES IN (2);
 ALTER TABLE ptnowner1 OWNER TO regress_ptnowner;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
+ALTER TABLE ptnowner1 REPLICA IDENTITY USING INDEX ptnowner1_i_key;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
 ALTER TABLE ptnowner OWNER TO regress_ptnowner;
 CREATE TEMP TABLE ptnowner_oldnodes AS
@@ -269,7 +271,12 @@ CREATE TEMP TABLE ptnowner_oldnodes AS
   JOIN pg_class AS c ON c.oid=tree.relid;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
+-- still can't repack without a replica identity
+ALTER TABLE ptnowner1 REPLICA IDENTITY DEFAULT;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
+SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
+  JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
 SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
   JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
 DROP TABLE ptnowner;
-- 
2.47.3


--m6zy3l65ushq557m
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-REPACK-do-not-require-LOGIN-privileges.patch"



^ permalink  raw  reply  [nested|flat] 58+ messages in thread

* [PATCH 1/2] REPACK: do not require the user to have REPLICATION
@ 2026-04-20 09:38  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 58+ messages in thread

From: Álvaro Herrera @ 2026-04-20 09:38 UTC (permalink / raw)

Although REPACK (CONCURRENTLY) uses replication slots, there is no
concern that the slot will leak data of other users, because the
MAINTAIN privilege on the table is required anyway; requiring
REPLICATION is user-unfriendly without providing any actual protection.

A related aspect is that the REPLICATION attribute is not needed to
prevent REPACK from stealing slots from logical replication, since
commit e76d8c749c31 made REPACK use a separate pool of replication
slots.

Because there are now successful concurrent repack runs in the
regression tests, we're forced to run test_plan_advice under
wal_level=replica.

Author: Antonin Houska <[email protected]>
Reported-by: Justin Pryzby <[email protected]>
Reviewed-by: Chao Li <[email protected]>
Discussion: https://postgr.es/m/aeJHPNmL4vVy3oPw@pryzbyj2023
---
 src/backend/commands/repack_worker.c          |  1 -
 .../test_plan_advice/t/001_replan_regress.pl  |  1 +
 src/test/regress/expected/cluster.out         | 20 +++++++++++++++++--
 src/test/regress/sql/cluster.sql              | 11 ++++++++--
 4 files changed, 28 insertions(+), 5 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index b17edd771e2..e4a4860805b 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -214,7 +214,6 @@ repack_setup_logical_decoding(Oid relid)
 	/*
 	 * Make sure we can use logical decoding.
 	 */
-	CheckSlotPermissions();
 	CheckLogicalDecodingRequirements(true);
 
 	/*
diff --git a/src/test/modules/test_plan_advice/t/001_replan_regress.pl b/src/test/modules/test_plan_advice/t/001_replan_regress.pl
index 38ffa4d11ae..452b179a665 100644
--- a/src/test/modules/test_plan_advice/t/001_replan_regress.pl
+++ b/src/test/modules/test_plan_advice/t/001_replan_regress.pl
@@ -18,6 +18,7 @@ $node->init();
 # Set up our desired configuration.
 $node->append_conf('postgresql.conf', <<EOM);
 shared_preload_libraries='test_plan_advice'
+wal_level=replica
 pg_plan_advice.always_explain_supplied_advice=false
 pg_plan_advice.feedback_warnings=true
 EOM
diff --git a/src/test/regress/expected/cluster.out b/src/test/regress/expected/cluster.out
index 6127b215a86..e17bc91fae1 100644
--- a/src/test/regress/expected/cluster.out
+++ b/src/test/regress/expected/cluster.out
@@ -543,15 +543,17 @@ ERROR:  REPACK (CONCURRENTLY) is not supported for partitioned tables
 HINT:  Consider running the command on individual partitions.
 DROP TABLE clstrpart;
 -- Ownership of partitions is checked
-CREATE TABLE ptnowner(i int unique) PARTITION BY LIST (i);
+CREATE TABLE ptnowner(i int unique not null) PARTITION BY LIST (i);
 CREATE INDEX ptnowner_i_idx ON ptnowner(i);
 CREATE TABLE ptnowner1 PARTITION OF ptnowner FOR VALUES IN (1);
-CREATE ROLE regress_ptnowner;
+CREATE ROLE regress_ptnowner LOGIN;
 CREATE TABLE ptnowner2 PARTITION OF ptnowner FOR VALUES IN (2);
 ALTER TABLE ptnowner1 OWNER TO regress_ptnowner;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
 ERROR:  permission denied for table ptnowner
+ALTER TABLE ptnowner1 REPLICA IDENTITY USING INDEX ptnowner1_i_key;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
 ALTER TABLE ptnowner OWNER TO regress_ptnowner;
 CREATE TEMP TABLE ptnowner_oldnodes AS
@@ -560,6 +562,11 @@ CREATE TEMP TABLE ptnowner_oldnodes AS
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
 WARNING:  permission denied to execute CLUSTER on "ptnowner2", skipping it
+-- still can't repack without a replica identity
+ALTER TABLE ptnowner1 REPLICA IDENTITY DEFAULT;
+REPACK (CONCURRENTLY) ptnowner1;
+ERROR:  cannot process relation "ptnowner1"
+HINT:  Relation "ptnowner1" has no identity index.
 RESET SESSION AUTHORIZATION;
 SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
   JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
@@ -570,6 +577,15 @@ SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
  ptnowner2 | t
 (3 rows)
 
+SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
+  JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
+  relname  | ?column? 
+-----------+----------
+ ptnowner  | t
+ ptnowner1 | f
+ ptnowner2 | t
+(3 rows)
+
 DROP TABLE ptnowner;
 DROP ROLE regress_ptnowner;
 -- Test CLUSTER with external tuplesorting
diff --git a/src/test/regress/sql/cluster.sql b/src/test/regress/sql/cluster.sql
index d14063a9683..1f471a8821a 100644
--- a/src/test/regress/sql/cluster.sql
+++ b/src/test/regress/sql/cluster.sql
@@ -254,14 +254,16 @@ REPACK (CONCURRENTLY) clstrpart;
 DROP TABLE clstrpart;
 
 -- Ownership of partitions is checked
-CREATE TABLE ptnowner(i int unique) PARTITION BY LIST (i);
+CREATE TABLE ptnowner(i int unique not null) PARTITION BY LIST (i);
 CREATE INDEX ptnowner_i_idx ON ptnowner(i);
 CREATE TABLE ptnowner1 PARTITION OF ptnowner FOR VALUES IN (1);
-CREATE ROLE regress_ptnowner;
+CREATE ROLE regress_ptnowner LOGIN;
 CREATE TABLE ptnowner2 PARTITION OF ptnowner FOR VALUES IN (2);
 ALTER TABLE ptnowner1 OWNER TO regress_ptnowner;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
+ALTER TABLE ptnowner1 REPLICA IDENTITY USING INDEX ptnowner1_i_key;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
 ALTER TABLE ptnowner OWNER TO regress_ptnowner;
 CREATE TEMP TABLE ptnowner_oldnodes AS
@@ -269,7 +271,12 @@ CREATE TEMP TABLE ptnowner_oldnodes AS
   JOIN pg_class AS c ON c.oid=tree.relid;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
+-- still can't repack without a replica identity
+ALTER TABLE ptnowner1 REPLICA IDENTITY DEFAULT;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
+SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
+  JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
 SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
   JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
 DROP TABLE ptnowner;
-- 
2.47.3


--m6zy3l65ushq557m
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-REPACK-do-not-require-LOGIN-privileges.patch"



^ permalink  raw  reply  [nested|flat] 58+ messages in thread

* [PATCH 1/2] REPACK: do not require the user to have REPLICATION
@ 2026-04-20 09:38  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 58+ messages in thread

From: Álvaro Herrera @ 2026-04-20 09:38 UTC (permalink / raw)

Although REPACK (CONCURRENTLY) uses replication slots, there is no
concern that the slot will leak data of other users, because the
MAINTAIN privilege on the table is required anyway; requiring
REPLICATION is user-unfriendly without providing any actual protection.

A related aspect is that the REPLICATION attribute is not needed to
prevent REPACK from stealing slots from logical replication, since
commit e76d8c749c31 made REPACK use a separate pool of replication
slots.

Because there are now successful concurrent repack runs in the
regression tests, we're forced to run test_plan_advice under
wal_level=replica.

Author: Antonin Houska <[email protected]>
Reported-by: Justin Pryzby <[email protected]>
Reviewed-by: Chao Li <[email protected]>
Discussion: https://postgr.es/m/aeJHPNmL4vVy3oPw@pryzbyj2023
---
 src/backend/commands/repack_worker.c          |  1 -
 .../test_plan_advice/t/001_replan_regress.pl  |  1 +
 src/test/regress/expected/cluster.out         | 20 +++++++++++++++++--
 src/test/regress/sql/cluster.sql              | 11 ++++++++--
 4 files changed, 28 insertions(+), 5 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index b17edd771e2..e4a4860805b 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -214,7 +214,6 @@ repack_setup_logical_decoding(Oid relid)
 	/*
 	 * Make sure we can use logical decoding.
 	 */
-	CheckSlotPermissions();
 	CheckLogicalDecodingRequirements(true);
 
 	/*
diff --git a/src/test/modules/test_plan_advice/t/001_replan_regress.pl b/src/test/modules/test_plan_advice/t/001_replan_regress.pl
index 38ffa4d11ae..452b179a665 100644
--- a/src/test/modules/test_plan_advice/t/001_replan_regress.pl
+++ b/src/test/modules/test_plan_advice/t/001_replan_regress.pl
@@ -18,6 +18,7 @@ $node->init();
 # Set up our desired configuration.
 $node->append_conf('postgresql.conf', <<EOM);
 shared_preload_libraries='test_plan_advice'
+wal_level=replica
 pg_plan_advice.always_explain_supplied_advice=false
 pg_plan_advice.feedback_warnings=true
 EOM
diff --git a/src/test/regress/expected/cluster.out b/src/test/regress/expected/cluster.out
index 6127b215a86..e17bc91fae1 100644
--- a/src/test/regress/expected/cluster.out
+++ b/src/test/regress/expected/cluster.out
@@ -543,15 +543,17 @@ ERROR:  REPACK (CONCURRENTLY) is not supported for partitioned tables
 HINT:  Consider running the command on individual partitions.
 DROP TABLE clstrpart;
 -- Ownership of partitions is checked
-CREATE TABLE ptnowner(i int unique) PARTITION BY LIST (i);
+CREATE TABLE ptnowner(i int unique not null) PARTITION BY LIST (i);
 CREATE INDEX ptnowner_i_idx ON ptnowner(i);
 CREATE TABLE ptnowner1 PARTITION OF ptnowner FOR VALUES IN (1);
-CREATE ROLE regress_ptnowner;
+CREATE ROLE regress_ptnowner LOGIN;
 CREATE TABLE ptnowner2 PARTITION OF ptnowner FOR VALUES IN (2);
 ALTER TABLE ptnowner1 OWNER TO regress_ptnowner;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
 ERROR:  permission denied for table ptnowner
+ALTER TABLE ptnowner1 REPLICA IDENTITY USING INDEX ptnowner1_i_key;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
 ALTER TABLE ptnowner OWNER TO regress_ptnowner;
 CREATE TEMP TABLE ptnowner_oldnodes AS
@@ -560,6 +562,11 @@ CREATE TEMP TABLE ptnowner_oldnodes AS
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
 WARNING:  permission denied to execute CLUSTER on "ptnowner2", skipping it
+-- still can't repack without a replica identity
+ALTER TABLE ptnowner1 REPLICA IDENTITY DEFAULT;
+REPACK (CONCURRENTLY) ptnowner1;
+ERROR:  cannot process relation "ptnowner1"
+HINT:  Relation "ptnowner1" has no identity index.
 RESET SESSION AUTHORIZATION;
 SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
   JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
@@ -570,6 +577,15 @@ SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
  ptnowner2 | t
 (3 rows)
 
+SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
+  JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
+  relname  | ?column? 
+-----------+----------
+ ptnowner  | t
+ ptnowner1 | f
+ ptnowner2 | t
+(3 rows)
+
 DROP TABLE ptnowner;
 DROP ROLE regress_ptnowner;
 -- Test CLUSTER with external tuplesorting
diff --git a/src/test/regress/sql/cluster.sql b/src/test/regress/sql/cluster.sql
index d14063a9683..1f471a8821a 100644
--- a/src/test/regress/sql/cluster.sql
+++ b/src/test/regress/sql/cluster.sql
@@ -254,14 +254,16 @@ REPACK (CONCURRENTLY) clstrpart;
 DROP TABLE clstrpart;
 
 -- Ownership of partitions is checked
-CREATE TABLE ptnowner(i int unique) PARTITION BY LIST (i);
+CREATE TABLE ptnowner(i int unique not null) PARTITION BY LIST (i);
 CREATE INDEX ptnowner_i_idx ON ptnowner(i);
 CREATE TABLE ptnowner1 PARTITION OF ptnowner FOR VALUES IN (1);
-CREATE ROLE regress_ptnowner;
+CREATE ROLE regress_ptnowner LOGIN;
 CREATE TABLE ptnowner2 PARTITION OF ptnowner FOR VALUES IN (2);
 ALTER TABLE ptnowner1 OWNER TO regress_ptnowner;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
+ALTER TABLE ptnowner1 REPLICA IDENTITY USING INDEX ptnowner1_i_key;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
 ALTER TABLE ptnowner OWNER TO regress_ptnowner;
 CREATE TEMP TABLE ptnowner_oldnodes AS
@@ -269,7 +271,12 @@ CREATE TEMP TABLE ptnowner_oldnodes AS
   JOIN pg_class AS c ON c.oid=tree.relid;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
+-- still can't repack without a replica identity
+ALTER TABLE ptnowner1 REPLICA IDENTITY DEFAULT;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
+SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
+  JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
 SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
   JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
 DROP TABLE ptnowner;
-- 
2.47.3


--m6zy3l65ushq557m
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-REPACK-do-not-require-LOGIN-privileges.patch"



^ permalink  raw  reply  [nested|flat] 58+ messages in thread

* [PATCH 1/2] REPACK: do not require the user to have REPLICATION
@ 2026-04-20 09:38  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 58+ messages in thread

From: Álvaro Herrera @ 2026-04-20 09:38 UTC (permalink / raw)

Although REPACK (CONCURRENTLY) uses replication slots, there is no
concern that the slot will leak data of other users, because the
MAINTAIN privilege on the table is required anyway; requiring
REPLICATION is user-unfriendly without providing any actual protection.

A related aspect is that the REPLICATION attribute is not needed to
prevent REPACK from stealing slots from logical replication, since
commit e76d8c749c31 made REPACK use a separate pool of replication
slots.

Because there are now successful concurrent repack runs in the
regression tests, we're forced to run test_plan_advice under
wal_level=replica.

Author: Antonin Houska <[email protected]>
Reported-by: Justin Pryzby <[email protected]>
Reviewed-by: Chao Li <[email protected]>
Discussion: https://postgr.es/m/aeJHPNmL4vVy3oPw@pryzbyj2023
---
 src/backend/commands/repack_worker.c          |  1 -
 .../test_plan_advice/t/001_replan_regress.pl  |  1 +
 src/test/regress/expected/cluster.out         | 20 +++++++++++++++++--
 src/test/regress/sql/cluster.sql              | 11 ++++++++--
 4 files changed, 28 insertions(+), 5 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index b17edd771e2..e4a4860805b 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -214,7 +214,6 @@ repack_setup_logical_decoding(Oid relid)
 	/*
 	 * Make sure we can use logical decoding.
 	 */
-	CheckSlotPermissions();
 	CheckLogicalDecodingRequirements(true);
 
 	/*
diff --git a/src/test/modules/test_plan_advice/t/001_replan_regress.pl b/src/test/modules/test_plan_advice/t/001_replan_regress.pl
index 38ffa4d11ae..452b179a665 100644
--- a/src/test/modules/test_plan_advice/t/001_replan_regress.pl
+++ b/src/test/modules/test_plan_advice/t/001_replan_regress.pl
@@ -18,6 +18,7 @@ $node->init();
 # Set up our desired configuration.
 $node->append_conf('postgresql.conf', <<EOM);
 shared_preload_libraries='test_plan_advice'
+wal_level=replica
 pg_plan_advice.always_explain_supplied_advice=false
 pg_plan_advice.feedback_warnings=true
 EOM
diff --git a/src/test/regress/expected/cluster.out b/src/test/regress/expected/cluster.out
index 6127b215a86..e17bc91fae1 100644
--- a/src/test/regress/expected/cluster.out
+++ b/src/test/regress/expected/cluster.out
@@ -543,15 +543,17 @@ ERROR:  REPACK (CONCURRENTLY) is not supported for partitioned tables
 HINT:  Consider running the command on individual partitions.
 DROP TABLE clstrpart;
 -- Ownership of partitions is checked
-CREATE TABLE ptnowner(i int unique) PARTITION BY LIST (i);
+CREATE TABLE ptnowner(i int unique not null) PARTITION BY LIST (i);
 CREATE INDEX ptnowner_i_idx ON ptnowner(i);
 CREATE TABLE ptnowner1 PARTITION OF ptnowner FOR VALUES IN (1);
-CREATE ROLE regress_ptnowner;
+CREATE ROLE regress_ptnowner LOGIN;
 CREATE TABLE ptnowner2 PARTITION OF ptnowner FOR VALUES IN (2);
 ALTER TABLE ptnowner1 OWNER TO regress_ptnowner;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
 ERROR:  permission denied for table ptnowner
+ALTER TABLE ptnowner1 REPLICA IDENTITY USING INDEX ptnowner1_i_key;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
 ALTER TABLE ptnowner OWNER TO regress_ptnowner;
 CREATE TEMP TABLE ptnowner_oldnodes AS
@@ -560,6 +562,11 @@ CREATE TEMP TABLE ptnowner_oldnodes AS
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
 WARNING:  permission denied to execute CLUSTER on "ptnowner2", skipping it
+-- still can't repack without a replica identity
+ALTER TABLE ptnowner1 REPLICA IDENTITY DEFAULT;
+REPACK (CONCURRENTLY) ptnowner1;
+ERROR:  cannot process relation "ptnowner1"
+HINT:  Relation "ptnowner1" has no identity index.
 RESET SESSION AUTHORIZATION;
 SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
   JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
@@ -570,6 +577,15 @@ SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
  ptnowner2 | t
 (3 rows)
 
+SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
+  JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
+  relname  | ?column? 
+-----------+----------
+ ptnowner  | t
+ ptnowner1 | f
+ ptnowner2 | t
+(3 rows)
+
 DROP TABLE ptnowner;
 DROP ROLE regress_ptnowner;
 -- Test CLUSTER with external tuplesorting
diff --git a/src/test/regress/sql/cluster.sql b/src/test/regress/sql/cluster.sql
index d14063a9683..1f471a8821a 100644
--- a/src/test/regress/sql/cluster.sql
+++ b/src/test/regress/sql/cluster.sql
@@ -254,14 +254,16 @@ REPACK (CONCURRENTLY) clstrpart;
 DROP TABLE clstrpart;
 
 -- Ownership of partitions is checked
-CREATE TABLE ptnowner(i int unique) PARTITION BY LIST (i);
+CREATE TABLE ptnowner(i int unique not null) PARTITION BY LIST (i);
 CREATE INDEX ptnowner_i_idx ON ptnowner(i);
 CREATE TABLE ptnowner1 PARTITION OF ptnowner FOR VALUES IN (1);
-CREATE ROLE regress_ptnowner;
+CREATE ROLE regress_ptnowner LOGIN;
 CREATE TABLE ptnowner2 PARTITION OF ptnowner FOR VALUES IN (2);
 ALTER TABLE ptnowner1 OWNER TO regress_ptnowner;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
+ALTER TABLE ptnowner1 REPLICA IDENTITY USING INDEX ptnowner1_i_key;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
 ALTER TABLE ptnowner OWNER TO regress_ptnowner;
 CREATE TEMP TABLE ptnowner_oldnodes AS
@@ -269,7 +271,12 @@ CREATE TEMP TABLE ptnowner_oldnodes AS
   JOIN pg_class AS c ON c.oid=tree.relid;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
+-- still can't repack without a replica identity
+ALTER TABLE ptnowner1 REPLICA IDENTITY DEFAULT;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
+SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
+  JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
 SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
   JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
 DROP TABLE ptnowner;
-- 
2.47.3


--m6zy3l65ushq557m
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-REPACK-do-not-require-LOGIN-privileges.patch"



^ permalink  raw  reply  [nested|flat] 58+ messages in thread

* [PATCH 1/2] REPACK: do not require the user to have REPLICATION
@ 2026-04-20 09:38  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 58+ messages in thread

From: Álvaro Herrera @ 2026-04-20 09:38 UTC (permalink / raw)

Although REPACK (CONCURRENTLY) uses replication slots, there is no
concern that the slot will leak data of other users, because the
MAINTAIN privilege on the table is required anyway; requiring
REPLICATION is user-unfriendly without providing any actual protection.

A related aspect is that the REPLICATION attribute is not needed to
prevent REPACK from stealing slots from logical replication, since
commit e76d8c749c31 made REPACK use a separate pool of replication
slots.

Because there are now successful concurrent repack runs in the
regression tests, we're forced to run test_plan_advice under
wal_level=replica.

Author: Antonin Houska <[email protected]>
Reported-by: Justin Pryzby <[email protected]>
Reviewed-by: Chao Li <[email protected]>
Discussion: https://postgr.es/m/aeJHPNmL4vVy3oPw@pryzbyj2023
---
 src/backend/commands/repack_worker.c          |  1 -
 .../test_plan_advice/t/001_replan_regress.pl  |  1 +
 src/test/regress/expected/cluster.out         | 20 +++++++++++++++++--
 src/test/regress/sql/cluster.sql              | 11 ++++++++--
 4 files changed, 28 insertions(+), 5 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index b17edd771e2..e4a4860805b 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -214,7 +214,6 @@ repack_setup_logical_decoding(Oid relid)
 	/*
 	 * Make sure we can use logical decoding.
 	 */
-	CheckSlotPermissions();
 	CheckLogicalDecodingRequirements(true);
 
 	/*
diff --git a/src/test/modules/test_plan_advice/t/001_replan_regress.pl b/src/test/modules/test_plan_advice/t/001_replan_regress.pl
index 38ffa4d11ae..452b179a665 100644
--- a/src/test/modules/test_plan_advice/t/001_replan_regress.pl
+++ b/src/test/modules/test_plan_advice/t/001_replan_regress.pl
@@ -18,6 +18,7 @@ $node->init();
 # Set up our desired configuration.
 $node->append_conf('postgresql.conf', <<EOM);
 shared_preload_libraries='test_plan_advice'
+wal_level=replica
 pg_plan_advice.always_explain_supplied_advice=false
 pg_plan_advice.feedback_warnings=true
 EOM
diff --git a/src/test/regress/expected/cluster.out b/src/test/regress/expected/cluster.out
index 6127b215a86..e17bc91fae1 100644
--- a/src/test/regress/expected/cluster.out
+++ b/src/test/regress/expected/cluster.out
@@ -543,15 +543,17 @@ ERROR:  REPACK (CONCURRENTLY) is not supported for partitioned tables
 HINT:  Consider running the command on individual partitions.
 DROP TABLE clstrpart;
 -- Ownership of partitions is checked
-CREATE TABLE ptnowner(i int unique) PARTITION BY LIST (i);
+CREATE TABLE ptnowner(i int unique not null) PARTITION BY LIST (i);
 CREATE INDEX ptnowner_i_idx ON ptnowner(i);
 CREATE TABLE ptnowner1 PARTITION OF ptnowner FOR VALUES IN (1);
-CREATE ROLE regress_ptnowner;
+CREATE ROLE regress_ptnowner LOGIN;
 CREATE TABLE ptnowner2 PARTITION OF ptnowner FOR VALUES IN (2);
 ALTER TABLE ptnowner1 OWNER TO regress_ptnowner;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
 ERROR:  permission denied for table ptnowner
+ALTER TABLE ptnowner1 REPLICA IDENTITY USING INDEX ptnowner1_i_key;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
 ALTER TABLE ptnowner OWNER TO regress_ptnowner;
 CREATE TEMP TABLE ptnowner_oldnodes AS
@@ -560,6 +562,11 @@ CREATE TEMP TABLE ptnowner_oldnodes AS
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
 WARNING:  permission denied to execute CLUSTER on "ptnowner2", skipping it
+-- still can't repack without a replica identity
+ALTER TABLE ptnowner1 REPLICA IDENTITY DEFAULT;
+REPACK (CONCURRENTLY) ptnowner1;
+ERROR:  cannot process relation "ptnowner1"
+HINT:  Relation "ptnowner1" has no identity index.
 RESET SESSION AUTHORIZATION;
 SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
   JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
@@ -570,6 +577,15 @@ SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
  ptnowner2 | t
 (3 rows)
 
+SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
+  JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
+  relname  | ?column? 
+-----------+----------
+ ptnowner  | t
+ ptnowner1 | f
+ ptnowner2 | t
+(3 rows)
+
 DROP TABLE ptnowner;
 DROP ROLE regress_ptnowner;
 -- Test CLUSTER with external tuplesorting
diff --git a/src/test/regress/sql/cluster.sql b/src/test/regress/sql/cluster.sql
index d14063a9683..1f471a8821a 100644
--- a/src/test/regress/sql/cluster.sql
+++ b/src/test/regress/sql/cluster.sql
@@ -254,14 +254,16 @@ REPACK (CONCURRENTLY) clstrpart;
 DROP TABLE clstrpart;
 
 -- Ownership of partitions is checked
-CREATE TABLE ptnowner(i int unique) PARTITION BY LIST (i);
+CREATE TABLE ptnowner(i int unique not null) PARTITION BY LIST (i);
 CREATE INDEX ptnowner_i_idx ON ptnowner(i);
 CREATE TABLE ptnowner1 PARTITION OF ptnowner FOR VALUES IN (1);
-CREATE ROLE regress_ptnowner;
+CREATE ROLE regress_ptnowner LOGIN;
 CREATE TABLE ptnowner2 PARTITION OF ptnowner FOR VALUES IN (2);
 ALTER TABLE ptnowner1 OWNER TO regress_ptnowner;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
+ALTER TABLE ptnowner1 REPLICA IDENTITY USING INDEX ptnowner1_i_key;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
 ALTER TABLE ptnowner OWNER TO regress_ptnowner;
 CREATE TEMP TABLE ptnowner_oldnodes AS
@@ -269,7 +271,12 @@ CREATE TEMP TABLE ptnowner_oldnodes AS
   JOIN pg_class AS c ON c.oid=tree.relid;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
+-- still can't repack without a replica identity
+ALTER TABLE ptnowner1 REPLICA IDENTITY DEFAULT;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
+SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
+  JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
 SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
   JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
 DROP TABLE ptnowner;
-- 
2.47.3


--m6zy3l65ushq557m
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-REPACK-do-not-require-LOGIN-privileges.patch"



^ permalink  raw  reply  [nested|flat] 58+ messages in thread

* [PATCH 1/2] REPACK: do not require the user to have REPLICATION
@ 2026-04-20 09:38  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 58+ messages in thread

From: Álvaro Herrera @ 2026-04-20 09:38 UTC (permalink / raw)

Although REPACK (CONCURRENTLY) uses replication slots, there is no
concern that the slot will leak data of other users, because the
MAINTAIN privilege on the table is required anyway; requiring
REPLICATION is user-unfriendly without providing any actual protection.

A related aspect is that the REPLICATION attribute is not needed to
prevent REPACK from stealing slots from logical replication, since
commit e76d8c749c31 made REPACK use a separate pool of replication
slots.

Because there are now successful concurrent repack runs in the
regression tests, we're forced to run test_plan_advice under
wal_level=replica.

Author: Antonin Houska <[email protected]>
Reported-by: Justin Pryzby <[email protected]>
Reviewed-by: Chao Li <[email protected]>
Discussion: https://postgr.es/m/aeJHPNmL4vVy3oPw@pryzbyj2023
---
 src/backend/commands/repack_worker.c          |  1 -
 .../test_plan_advice/t/001_replan_regress.pl  |  1 +
 src/test/regress/expected/cluster.out         | 20 +++++++++++++++++--
 src/test/regress/sql/cluster.sql              | 11 ++++++++--
 4 files changed, 28 insertions(+), 5 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index b17edd771e2..e4a4860805b 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -214,7 +214,6 @@ repack_setup_logical_decoding(Oid relid)
 	/*
 	 * Make sure we can use logical decoding.
 	 */
-	CheckSlotPermissions();
 	CheckLogicalDecodingRequirements(true);
 
 	/*
diff --git a/src/test/modules/test_plan_advice/t/001_replan_regress.pl b/src/test/modules/test_plan_advice/t/001_replan_regress.pl
index 38ffa4d11ae..452b179a665 100644
--- a/src/test/modules/test_plan_advice/t/001_replan_regress.pl
+++ b/src/test/modules/test_plan_advice/t/001_replan_regress.pl
@@ -18,6 +18,7 @@ $node->init();
 # Set up our desired configuration.
 $node->append_conf('postgresql.conf', <<EOM);
 shared_preload_libraries='test_plan_advice'
+wal_level=replica
 pg_plan_advice.always_explain_supplied_advice=false
 pg_plan_advice.feedback_warnings=true
 EOM
diff --git a/src/test/regress/expected/cluster.out b/src/test/regress/expected/cluster.out
index 6127b215a86..e17bc91fae1 100644
--- a/src/test/regress/expected/cluster.out
+++ b/src/test/regress/expected/cluster.out
@@ -543,15 +543,17 @@ ERROR:  REPACK (CONCURRENTLY) is not supported for partitioned tables
 HINT:  Consider running the command on individual partitions.
 DROP TABLE clstrpart;
 -- Ownership of partitions is checked
-CREATE TABLE ptnowner(i int unique) PARTITION BY LIST (i);
+CREATE TABLE ptnowner(i int unique not null) PARTITION BY LIST (i);
 CREATE INDEX ptnowner_i_idx ON ptnowner(i);
 CREATE TABLE ptnowner1 PARTITION OF ptnowner FOR VALUES IN (1);
-CREATE ROLE regress_ptnowner;
+CREATE ROLE regress_ptnowner LOGIN;
 CREATE TABLE ptnowner2 PARTITION OF ptnowner FOR VALUES IN (2);
 ALTER TABLE ptnowner1 OWNER TO regress_ptnowner;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
 ERROR:  permission denied for table ptnowner
+ALTER TABLE ptnowner1 REPLICA IDENTITY USING INDEX ptnowner1_i_key;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
 ALTER TABLE ptnowner OWNER TO regress_ptnowner;
 CREATE TEMP TABLE ptnowner_oldnodes AS
@@ -560,6 +562,11 @@ CREATE TEMP TABLE ptnowner_oldnodes AS
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
 WARNING:  permission denied to execute CLUSTER on "ptnowner2", skipping it
+-- still can't repack without a replica identity
+ALTER TABLE ptnowner1 REPLICA IDENTITY DEFAULT;
+REPACK (CONCURRENTLY) ptnowner1;
+ERROR:  cannot process relation "ptnowner1"
+HINT:  Relation "ptnowner1" has no identity index.
 RESET SESSION AUTHORIZATION;
 SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
   JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
@@ -570,6 +577,15 @@ SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
  ptnowner2 | t
 (3 rows)
 
+SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
+  JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
+  relname  | ?column? 
+-----------+----------
+ ptnowner  | t
+ ptnowner1 | f
+ ptnowner2 | t
+(3 rows)
+
 DROP TABLE ptnowner;
 DROP ROLE regress_ptnowner;
 -- Test CLUSTER with external tuplesorting
diff --git a/src/test/regress/sql/cluster.sql b/src/test/regress/sql/cluster.sql
index d14063a9683..1f471a8821a 100644
--- a/src/test/regress/sql/cluster.sql
+++ b/src/test/regress/sql/cluster.sql
@@ -254,14 +254,16 @@ REPACK (CONCURRENTLY) clstrpart;
 DROP TABLE clstrpart;
 
 -- Ownership of partitions is checked
-CREATE TABLE ptnowner(i int unique) PARTITION BY LIST (i);
+CREATE TABLE ptnowner(i int unique not null) PARTITION BY LIST (i);
 CREATE INDEX ptnowner_i_idx ON ptnowner(i);
 CREATE TABLE ptnowner1 PARTITION OF ptnowner FOR VALUES IN (1);
-CREATE ROLE regress_ptnowner;
+CREATE ROLE regress_ptnowner LOGIN;
 CREATE TABLE ptnowner2 PARTITION OF ptnowner FOR VALUES IN (2);
 ALTER TABLE ptnowner1 OWNER TO regress_ptnowner;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
+ALTER TABLE ptnowner1 REPLICA IDENTITY USING INDEX ptnowner1_i_key;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
 ALTER TABLE ptnowner OWNER TO regress_ptnowner;
 CREATE TEMP TABLE ptnowner_oldnodes AS
@@ -269,7 +271,12 @@ CREATE TEMP TABLE ptnowner_oldnodes AS
   JOIN pg_class AS c ON c.oid=tree.relid;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
+-- still can't repack without a replica identity
+ALTER TABLE ptnowner1 REPLICA IDENTITY DEFAULT;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
+SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
+  JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
 SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
   JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
 DROP TABLE ptnowner;
-- 
2.47.3


--m6zy3l65ushq557m
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-REPACK-do-not-require-LOGIN-privileges.patch"



^ permalink  raw  reply  [nested|flat] 58+ messages in thread

* [PATCH 1/2] REPACK: do not require the user to have REPLICATION
@ 2026-04-20 09:38  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 58+ messages in thread

From: Álvaro Herrera @ 2026-04-20 09:38 UTC (permalink / raw)

Although REPACK (CONCURRENTLY) uses replication slots, there is no
concern that the slot will leak data of other users, because the
MAINTAIN privilege on the table is required anyway; requiring
REPLICATION is user-unfriendly without providing any actual protection.

A related aspect is that the REPLICATION attribute is not needed to
prevent REPACK from stealing slots from logical replication, since
commit e76d8c749c31 made REPACK use a separate pool of replication
slots.

Because there are now successful concurrent repack runs in the
regression tests, we're forced to run test_plan_advice under
wal_level=replica.

Author: Antonin Houska <[email protected]>
Reported-by: Justin Pryzby <[email protected]>
Reviewed-by: Chao Li <[email protected]>
Discussion: https://postgr.es/m/aeJHPNmL4vVy3oPw@pryzbyj2023
---
 src/backend/commands/repack_worker.c          |  1 -
 .../test_plan_advice/t/001_replan_regress.pl  |  1 +
 src/test/regress/expected/cluster.out         | 20 +++++++++++++++++--
 src/test/regress/sql/cluster.sql              | 11 ++++++++--
 4 files changed, 28 insertions(+), 5 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index b17edd771e2..e4a4860805b 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -214,7 +214,6 @@ repack_setup_logical_decoding(Oid relid)
 	/*
 	 * Make sure we can use logical decoding.
 	 */
-	CheckSlotPermissions();
 	CheckLogicalDecodingRequirements(true);
 
 	/*
diff --git a/src/test/modules/test_plan_advice/t/001_replan_regress.pl b/src/test/modules/test_plan_advice/t/001_replan_regress.pl
index 38ffa4d11ae..452b179a665 100644
--- a/src/test/modules/test_plan_advice/t/001_replan_regress.pl
+++ b/src/test/modules/test_plan_advice/t/001_replan_regress.pl
@@ -18,6 +18,7 @@ $node->init();
 # Set up our desired configuration.
 $node->append_conf('postgresql.conf', <<EOM);
 shared_preload_libraries='test_plan_advice'
+wal_level=replica
 pg_plan_advice.always_explain_supplied_advice=false
 pg_plan_advice.feedback_warnings=true
 EOM
diff --git a/src/test/regress/expected/cluster.out b/src/test/regress/expected/cluster.out
index 6127b215a86..e17bc91fae1 100644
--- a/src/test/regress/expected/cluster.out
+++ b/src/test/regress/expected/cluster.out
@@ -543,15 +543,17 @@ ERROR:  REPACK (CONCURRENTLY) is not supported for partitioned tables
 HINT:  Consider running the command on individual partitions.
 DROP TABLE clstrpart;
 -- Ownership of partitions is checked
-CREATE TABLE ptnowner(i int unique) PARTITION BY LIST (i);
+CREATE TABLE ptnowner(i int unique not null) PARTITION BY LIST (i);
 CREATE INDEX ptnowner_i_idx ON ptnowner(i);
 CREATE TABLE ptnowner1 PARTITION OF ptnowner FOR VALUES IN (1);
-CREATE ROLE regress_ptnowner;
+CREATE ROLE regress_ptnowner LOGIN;
 CREATE TABLE ptnowner2 PARTITION OF ptnowner FOR VALUES IN (2);
 ALTER TABLE ptnowner1 OWNER TO regress_ptnowner;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
 ERROR:  permission denied for table ptnowner
+ALTER TABLE ptnowner1 REPLICA IDENTITY USING INDEX ptnowner1_i_key;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
 ALTER TABLE ptnowner OWNER TO regress_ptnowner;
 CREATE TEMP TABLE ptnowner_oldnodes AS
@@ -560,6 +562,11 @@ CREATE TEMP TABLE ptnowner_oldnodes AS
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
 WARNING:  permission denied to execute CLUSTER on "ptnowner2", skipping it
+-- still can't repack without a replica identity
+ALTER TABLE ptnowner1 REPLICA IDENTITY DEFAULT;
+REPACK (CONCURRENTLY) ptnowner1;
+ERROR:  cannot process relation "ptnowner1"
+HINT:  Relation "ptnowner1" has no identity index.
 RESET SESSION AUTHORIZATION;
 SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
   JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
@@ -570,6 +577,15 @@ SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
  ptnowner2 | t
 (3 rows)
 
+SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
+  JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
+  relname  | ?column? 
+-----------+----------
+ ptnowner  | t
+ ptnowner1 | f
+ ptnowner2 | t
+(3 rows)
+
 DROP TABLE ptnowner;
 DROP ROLE regress_ptnowner;
 -- Test CLUSTER with external tuplesorting
diff --git a/src/test/regress/sql/cluster.sql b/src/test/regress/sql/cluster.sql
index d14063a9683..1f471a8821a 100644
--- a/src/test/regress/sql/cluster.sql
+++ b/src/test/regress/sql/cluster.sql
@@ -254,14 +254,16 @@ REPACK (CONCURRENTLY) clstrpart;
 DROP TABLE clstrpart;
 
 -- Ownership of partitions is checked
-CREATE TABLE ptnowner(i int unique) PARTITION BY LIST (i);
+CREATE TABLE ptnowner(i int unique not null) PARTITION BY LIST (i);
 CREATE INDEX ptnowner_i_idx ON ptnowner(i);
 CREATE TABLE ptnowner1 PARTITION OF ptnowner FOR VALUES IN (1);
-CREATE ROLE regress_ptnowner;
+CREATE ROLE regress_ptnowner LOGIN;
 CREATE TABLE ptnowner2 PARTITION OF ptnowner FOR VALUES IN (2);
 ALTER TABLE ptnowner1 OWNER TO regress_ptnowner;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
+ALTER TABLE ptnowner1 REPLICA IDENTITY USING INDEX ptnowner1_i_key;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
 ALTER TABLE ptnowner OWNER TO regress_ptnowner;
 CREATE TEMP TABLE ptnowner_oldnodes AS
@@ -269,7 +271,12 @@ CREATE TEMP TABLE ptnowner_oldnodes AS
   JOIN pg_class AS c ON c.oid=tree.relid;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
+-- still can't repack without a replica identity
+ALTER TABLE ptnowner1 REPLICA IDENTITY DEFAULT;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
+SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
+  JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
 SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
   JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
 DROP TABLE ptnowner;
-- 
2.47.3


--m6zy3l65ushq557m
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-REPACK-do-not-require-LOGIN-privileges.patch"



^ permalink  raw  reply  [nested|flat] 58+ messages in thread

* [PATCH 1/2] REPACK: do not require the user to have REPLICATION
@ 2026-04-20 09:38  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 58+ messages in thread

From: Álvaro Herrera @ 2026-04-20 09:38 UTC (permalink / raw)

Although REPACK (CONCURRENTLY) uses replication slots, there is no
concern that the slot will leak data of other users, because the
MAINTAIN privilege on the table is required anyway; requiring
REPLICATION is user-unfriendly without providing any actual protection.

A related aspect is that the REPLICATION attribute is not needed to
prevent REPACK from stealing slots from logical replication, since
commit e76d8c749c31 made REPACK use a separate pool of replication
slots.

Because there are now successful concurrent repack runs in the
regression tests, we're forced to run test_plan_advice under
wal_level=replica.

Author: Antonin Houska <[email protected]>
Reported-by: Justin Pryzby <[email protected]>
Reviewed-by: Chao Li <[email protected]>
Discussion: https://postgr.es/m/aeJHPNmL4vVy3oPw@pryzbyj2023
---
 src/backend/commands/repack_worker.c          |  1 -
 .../test_plan_advice/t/001_replan_regress.pl  |  1 +
 src/test/regress/expected/cluster.out         | 20 +++++++++++++++++--
 src/test/regress/sql/cluster.sql              | 11 ++++++++--
 4 files changed, 28 insertions(+), 5 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index b17edd771e2..e4a4860805b 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -214,7 +214,6 @@ repack_setup_logical_decoding(Oid relid)
 	/*
 	 * Make sure we can use logical decoding.
 	 */
-	CheckSlotPermissions();
 	CheckLogicalDecodingRequirements(true);
 
 	/*
diff --git a/src/test/modules/test_plan_advice/t/001_replan_regress.pl b/src/test/modules/test_plan_advice/t/001_replan_regress.pl
index 38ffa4d11ae..452b179a665 100644
--- a/src/test/modules/test_plan_advice/t/001_replan_regress.pl
+++ b/src/test/modules/test_plan_advice/t/001_replan_regress.pl
@@ -18,6 +18,7 @@ $node->init();
 # Set up our desired configuration.
 $node->append_conf('postgresql.conf', <<EOM);
 shared_preload_libraries='test_plan_advice'
+wal_level=replica
 pg_plan_advice.always_explain_supplied_advice=false
 pg_plan_advice.feedback_warnings=true
 EOM
diff --git a/src/test/regress/expected/cluster.out b/src/test/regress/expected/cluster.out
index 6127b215a86..e17bc91fae1 100644
--- a/src/test/regress/expected/cluster.out
+++ b/src/test/regress/expected/cluster.out
@@ -543,15 +543,17 @@ ERROR:  REPACK (CONCURRENTLY) is not supported for partitioned tables
 HINT:  Consider running the command on individual partitions.
 DROP TABLE clstrpart;
 -- Ownership of partitions is checked
-CREATE TABLE ptnowner(i int unique) PARTITION BY LIST (i);
+CREATE TABLE ptnowner(i int unique not null) PARTITION BY LIST (i);
 CREATE INDEX ptnowner_i_idx ON ptnowner(i);
 CREATE TABLE ptnowner1 PARTITION OF ptnowner FOR VALUES IN (1);
-CREATE ROLE regress_ptnowner;
+CREATE ROLE regress_ptnowner LOGIN;
 CREATE TABLE ptnowner2 PARTITION OF ptnowner FOR VALUES IN (2);
 ALTER TABLE ptnowner1 OWNER TO regress_ptnowner;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
 ERROR:  permission denied for table ptnowner
+ALTER TABLE ptnowner1 REPLICA IDENTITY USING INDEX ptnowner1_i_key;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
 ALTER TABLE ptnowner OWNER TO regress_ptnowner;
 CREATE TEMP TABLE ptnowner_oldnodes AS
@@ -560,6 +562,11 @@ CREATE TEMP TABLE ptnowner_oldnodes AS
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
 WARNING:  permission denied to execute CLUSTER on "ptnowner2", skipping it
+-- still can't repack without a replica identity
+ALTER TABLE ptnowner1 REPLICA IDENTITY DEFAULT;
+REPACK (CONCURRENTLY) ptnowner1;
+ERROR:  cannot process relation "ptnowner1"
+HINT:  Relation "ptnowner1" has no identity index.
 RESET SESSION AUTHORIZATION;
 SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
   JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
@@ -570,6 +577,15 @@ SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
  ptnowner2 | t
 (3 rows)
 
+SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
+  JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
+  relname  | ?column? 
+-----------+----------
+ ptnowner  | t
+ ptnowner1 | f
+ ptnowner2 | t
+(3 rows)
+
 DROP TABLE ptnowner;
 DROP ROLE regress_ptnowner;
 -- Test CLUSTER with external tuplesorting
diff --git a/src/test/regress/sql/cluster.sql b/src/test/regress/sql/cluster.sql
index d14063a9683..1f471a8821a 100644
--- a/src/test/regress/sql/cluster.sql
+++ b/src/test/regress/sql/cluster.sql
@@ -254,14 +254,16 @@ REPACK (CONCURRENTLY) clstrpart;
 DROP TABLE clstrpart;
 
 -- Ownership of partitions is checked
-CREATE TABLE ptnowner(i int unique) PARTITION BY LIST (i);
+CREATE TABLE ptnowner(i int unique not null) PARTITION BY LIST (i);
 CREATE INDEX ptnowner_i_idx ON ptnowner(i);
 CREATE TABLE ptnowner1 PARTITION OF ptnowner FOR VALUES IN (1);
-CREATE ROLE regress_ptnowner;
+CREATE ROLE regress_ptnowner LOGIN;
 CREATE TABLE ptnowner2 PARTITION OF ptnowner FOR VALUES IN (2);
 ALTER TABLE ptnowner1 OWNER TO regress_ptnowner;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
+ALTER TABLE ptnowner1 REPLICA IDENTITY USING INDEX ptnowner1_i_key;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
 ALTER TABLE ptnowner OWNER TO regress_ptnowner;
 CREATE TEMP TABLE ptnowner_oldnodes AS
@@ -269,7 +271,12 @@ CREATE TEMP TABLE ptnowner_oldnodes AS
   JOIN pg_class AS c ON c.oid=tree.relid;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
+-- still can't repack without a replica identity
+ALTER TABLE ptnowner1 REPLICA IDENTITY DEFAULT;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
+SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
+  JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
 SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
   JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
 DROP TABLE ptnowner;
-- 
2.47.3


--m6zy3l65ushq557m
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-REPACK-do-not-require-LOGIN-privileges.patch"



^ permalink  raw  reply  [nested|flat] 58+ messages in thread

* [PATCH 1/2] REPACK: do not require the user to have REPLICATION
@ 2026-04-20 09:38  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 58+ messages in thread

From: Álvaro Herrera @ 2026-04-20 09:38 UTC (permalink / raw)

Although REPACK (CONCURRENTLY) uses replication slots, there is no
concern that the slot will leak data of other users, because the
MAINTAIN privilege on the table is required anyway; requiring
REPLICATION is user-unfriendly without providing any actual protection.

A related aspect is that the REPLICATION attribute is not needed to
prevent REPACK from stealing slots from logical replication, since
commit e76d8c749c31 made REPACK use a separate pool of replication
slots.

Because there are now successful concurrent repack runs in the
regression tests, we're forced to run test_plan_advice under
wal_level=replica.

Author: Antonin Houska <[email protected]>
Reported-by: Justin Pryzby <[email protected]>
Reviewed-by: Chao Li <[email protected]>
Discussion: https://postgr.es/m/aeJHPNmL4vVy3oPw@pryzbyj2023
---
 src/backend/commands/repack_worker.c          |  1 -
 .../test_plan_advice/t/001_replan_regress.pl  |  1 +
 src/test/regress/expected/cluster.out         | 20 +++++++++++++++++--
 src/test/regress/sql/cluster.sql              | 11 ++++++++--
 4 files changed, 28 insertions(+), 5 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index b17edd771e2..e4a4860805b 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -214,7 +214,6 @@ repack_setup_logical_decoding(Oid relid)
 	/*
 	 * Make sure we can use logical decoding.
 	 */
-	CheckSlotPermissions();
 	CheckLogicalDecodingRequirements(true);
 
 	/*
diff --git a/src/test/modules/test_plan_advice/t/001_replan_regress.pl b/src/test/modules/test_plan_advice/t/001_replan_regress.pl
index 38ffa4d11ae..452b179a665 100644
--- a/src/test/modules/test_plan_advice/t/001_replan_regress.pl
+++ b/src/test/modules/test_plan_advice/t/001_replan_regress.pl
@@ -18,6 +18,7 @@ $node->init();
 # Set up our desired configuration.
 $node->append_conf('postgresql.conf', <<EOM);
 shared_preload_libraries='test_plan_advice'
+wal_level=replica
 pg_plan_advice.always_explain_supplied_advice=false
 pg_plan_advice.feedback_warnings=true
 EOM
diff --git a/src/test/regress/expected/cluster.out b/src/test/regress/expected/cluster.out
index 6127b215a86..e17bc91fae1 100644
--- a/src/test/regress/expected/cluster.out
+++ b/src/test/regress/expected/cluster.out
@@ -543,15 +543,17 @@ ERROR:  REPACK (CONCURRENTLY) is not supported for partitioned tables
 HINT:  Consider running the command on individual partitions.
 DROP TABLE clstrpart;
 -- Ownership of partitions is checked
-CREATE TABLE ptnowner(i int unique) PARTITION BY LIST (i);
+CREATE TABLE ptnowner(i int unique not null) PARTITION BY LIST (i);
 CREATE INDEX ptnowner_i_idx ON ptnowner(i);
 CREATE TABLE ptnowner1 PARTITION OF ptnowner FOR VALUES IN (1);
-CREATE ROLE regress_ptnowner;
+CREATE ROLE regress_ptnowner LOGIN;
 CREATE TABLE ptnowner2 PARTITION OF ptnowner FOR VALUES IN (2);
 ALTER TABLE ptnowner1 OWNER TO regress_ptnowner;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
 ERROR:  permission denied for table ptnowner
+ALTER TABLE ptnowner1 REPLICA IDENTITY USING INDEX ptnowner1_i_key;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
 ALTER TABLE ptnowner OWNER TO regress_ptnowner;
 CREATE TEMP TABLE ptnowner_oldnodes AS
@@ -560,6 +562,11 @@ CREATE TEMP TABLE ptnowner_oldnodes AS
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
 WARNING:  permission denied to execute CLUSTER on "ptnowner2", skipping it
+-- still can't repack without a replica identity
+ALTER TABLE ptnowner1 REPLICA IDENTITY DEFAULT;
+REPACK (CONCURRENTLY) ptnowner1;
+ERROR:  cannot process relation "ptnowner1"
+HINT:  Relation "ptnowner1" has no identity index.
 RESET SESSION AUTHORIZATION;
 SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
   JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
@@ -570,6 +577,15 @@ SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
  ptnowner2 | t
 (3 rows)
 
+SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
+  JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
+  relname  | ?column? 
+-----------+----------
+ ptnowner  | t
+ ptnowner1 | f
+ ptnowner2 | t
+(3 rows)
+
 DROP TABLE ptnowner;
 DROP ROLE regress_ptnowner;
 -- Test CLUSTER with external tuplesorting
diff --git a/src/test/regress/sql/cluster.sql b/src/test/regress/sql/cluster.sql
index d14063a9683..1f471a8821a 100644
--- a/src/test/regress/sql/cluster.sql
+++ b/src/test/regress/sql/cluster.sql
@@ -254,14 +254,16 @@ REPACK (CONCURRENTLY) clstrpart;
 DROP TABLE clstrpart;
 
 -- Ownership of partitions is checked
-CREATE TABLE ptnowner(i int unique) PARTITION BY LIST (i);
+CREATE TABLE ptnowner(i int unique not null) PARTITION BY LIST (i);
 CREATE INDEX ptnowner_i_idx ON ptnowner(i);
 CREATE TABLE ptnowner1 PARTITION OF ptnowner FOR VALUES IN (1);
-CREATE ROLE regress_ptnowner;
+CREATE ROLE regress_ptnowner LOGIN;
 CREATE TABLE ptnowner2 PARTITION OF ptnowner FOR VALUES IN (2);
 ALTER TABLE ptnowner1 OWNER TO regress_ptnowner;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
+ALTER TABLE ptnowner1 REPLICA IDENTITY USING INDEX ptnowner1_i_key;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
 ALTER TABLE ptnowner OWNER TO regress_ptnowner;
 CREATE TEMP TABLE ptnowner_oldnodes AS
@@ -269,7 +271,12 @@ CREATE TEMP TABLE ptnowner_oldnodes AS
   JOIN pg_class AS c ON c.oid=tree.relid;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
+-- still can't repack without a replica identity
+ALTER TABLE ptnowner1 REPLICA IDENTITY DEFAULT;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
+SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
+  JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
 SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
   JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
 DROP TABLE ptnowner;
-- 
2.47.3


--m6zy3l65ushq557m
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-REPACK-do-not-require-LOGIN-privileges.patch"



^ permalink  raw  reply  [nested|flat] 58+ messages in thread

* [PATCH 1/2] REPACK: do not require the user to have REPLICATION
@ 2026-04-20 09:38  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 58+ messages in thread

From: Álvaro Herrera @ 2026-04-20 09:38 UTC (permalink / raw)

Although REPACK (CONCURRENTLY) uses replication slots, there is no
concern that the slot will leak data of other users, because the
MAINTAIN privilege on the table is required anyway; requiring
REPLICATION is user-unfriendly without providing any actual protection.

A related aspect is that the REPLICATION attribute is not needed to
prevent REPACK from stealing slots from logical replication, since
commit e76d8c749c31 made REPACK use a separate pool of replication
slots.

Because there are now successful concurrent repack runs in the
regression tests, we're forced to run test_plan_advice under
wal_level=replica.

Author: Antonin Houska <[email protected]>
Reported-by: Justin Pryzby <[email protected]>
Reviewed-by: Chao Li <[email protected]>
Discussion: https://postgr.es/m/aeJHPNmL4vVy3oPw@pryzbyj2023
---
 src/backend/commands/repack_worker.c          |  1 -
 .../test_plan_advice/t/001_replan_regress.pl  |  1 +
 src/test/regress/expected/cluster.out         | 20 +++++++++++++++++--
 src/test/regress/sql/cluster.sql              | 11 ++++++++--
 4 files changed, 28 insertions(+), 5 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index b17edd771e2..e4a4860805b 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -214,7 +214,6 @@ repack_setup_logical_decoding(Oid relid)
 	/*
 	 * Make sure we can use logical decoding.
 	 */
-	CheckSlotPermissions();
 	CheckLogicalDecodingRequirements(true);
 
 	/*
diff --git a/src/test/modules/test_plan_advice/t/001_replan_regress.pl b/src/test/modules/test_plan_advice/t/001_replan_regress.pl
index 38ffa4d11ae..452b179a665 100644
--- a/src/test/modules/test_plan_advice/t/001_replan_regress.pl
+++ b/src/test/modules/test_plan_advice/t/001_replan_regress.pl
@@ -18,6 +18,7 @@ $node->init();
 # Set up our desired configuration.
 $node->append_conf('postgresql.conf', <<EOM);
 shared_preload_libraries='test_plan_advice'
+wal_level=replica
 pg_plan_advice.always_explain_supplied_advice=false
 pg_plan_advice.feedback_warnings=true
 EOM
diff --git a/src/test/regress/expected/cluster.out b/src/test/regress/expected/cluster.out
index 6127b215a86..e17bc91fae1 100644
--- a/src/test/regress/expected/cluster.out
+++ b/src/test/regress/expected/cluster.out
@@ -543,15 +543,17 @@ ERROR:  REPACK (CONCURRENTLY) is not supported for partitioned tables
 HINT:  Consider running the command on individual partitions.
 DROP TABLE clstrpart;
 -- Ownership of partitions is checked
-CREATE TABLE ptnowner(i int unique) PARTITION BY LIST (i);
+CREATE TABLE ptnowner(i int unique not null) PARTITION BY LIST (i);
 CREATE INDEX ptnowner_i_idx ON ptnowner(i);
 CREATE TABLE ptnowner1 PARTITION OF ptnowner FOR VALUES IN (1);
-CREATE ROLE regress_ptnowner;
+CREATE ROLE regress_ptnowner LOGIN;
 CREATE TABLE ptnowner2 PARTITION OF ptnowner FOR VALUES IN (2);
 ALTER TABLE ptnowner1 OWNER TO regress_ptnowner;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
 ERROR:  permission denied for table ptnowner
+ALTER TABLE ptnowner1 REPLICA IDENTITY USING INDEX ptnowner1_i_key;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
 ALTER TABLE ptnowner OWNER TO regress_ptnowner;
 CREATE TEMP TABLE ptnowner_oldnodes AS
@@ -560,6 +562,11 @@ CREATE TEMP TABLE ptnowner_oldnodes AS
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
 WARNING:  permission denied to execute CLUSTER on "ptnowner2", skipping it
+-- still can't repack without a replica identity
+ALTER TABLE ptnowner1 REPLICA IDENTITY DEFAULT;
+REPACK (CONCURRENTLY) ptnowner1;
+ERROR:  cannot process relation "ptnowner1"
+HINT:  Relation "ptnowner1" has no identity index.
 RESET SESSION AUTHORIZATION;
 SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
   JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
@@ -570,6 +577,15 @@ SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
  ptnowner2 | t
 (3 rows)
 
+SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
+  JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
+  relname  | ?column? 
+-----------+----------
+ ptnowner  | t
+ ptnowner1 | f
+ ptnowner2 | t
+(3 rows)
+
 DROP TABLE ptnowner;
 DROP ROLE regress_ptnowner;
 -- Test CLUSTER with external tuplesorting
diff --git a/src/test/regress/sql/cluster.sql b/src/test/regress/sql/cluster.sql
index d14063a9683..1f471a8821a 100644
--- a/src/test/regress/sql/cluster.sql
+++ b/src/test/regress/sql/cluster.sql
@@ -254,14 +254,16 @@ REPACK (CONCURRENTLY) clstrpart;
 DROP TABLE clstrpart;
 
 -- Ownership of partitions is checked
-CREATE TABLE ptnowner(i int unique) PARTITION BY LIST (i);
+CREATE TABLE ptnowner(i int unique not null) PARTITION BY LIST (i);
 CREATE INDEX ptnowner_i_idx ON ptnowner(i);
 CREATE TABLE ptnowner1 PARTITION OF ptnowner FOR VALUES IN (1);
-CREATE ROLE regress_ptnowner;
+CREATE ROLE regress_ptnowner LOGIN;
 CREATE TABLE ptnowner2 PARTITION OF ptnowner FOR VALUES IN (2);
 ALTER TABLE ptnowner1 OWNER TO regress_ptnowner;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
+ALTER TABLE ptnowner1 REPLICA IDENTITY USING INDEX ptnowner1_i_key;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
 ALTER TABLE ptnowner OWNER TO regress_ptnowner;
 CREATE TEMP TABLE ptnowner_oldnodes AS
@@ -269,7 +271,12 @@ CREATE TEMP TABLE ptnowner_oldnodes AS
   JOIN pg_class AS c ON c.oid=tree.relid;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
+-- still can't repack without a replica identity
+ALTER TABLE ptnowner1 REPLICA IDENTITY DEFAULT;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
+SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
+  JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
 SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
   JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
 DROP TABLE ptnowner;
-- 
2.47.3


--m6zy3l65ushq557m
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-REPACK-do-not-require-LOGIN-privileges.patch"



^ permalink  raw  reply  [nested|flat] 58+ messages in thread

* [PATCH 1/2] REPACK: do not require the user to have REPLICATION
@ 2026-04-20 09:38  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 58+ messages in thread

From: Álvaro Herrera @ 2026-04-20 09:38 UTC (permalink / raw)

Although REPACK (CONCURRENTLY) uses replication slots, there is no
concern that the slot will leak data of other users, because the
MAINTAIN privilege on the table is required anyway; requiring
REPLICATION is user-unfriendly without providing any actual protection.

A related aspect is that the REPLICATION attribute is not needed to
prevent REPACK from stealing slots from logical replication, since
commit e76d8c749c31 made REPACK use a separate pool of replication
slots.

Because there are now successful concurrent repack runs in the
regression tests, we're forced to run test_plan_advice under
wal_level=replica.

Author: Antonin Houska <[email protected]>
Reported-by: Justin Pryzby <[email protected]>
Reviewed-by: Chao Li <[email protected]>
Discussion: https://postgr.es/m/aeJHPNmL4vVy3oPw@pryzbyj2023
---
 src/backend/commands/repack_worker.c          |  1 -
 .../test_plan_advice/t/001_replan_regress.pl  |  1 +
 src/test/regress/expected/cluster.out         | 20 +++++++++++++++++--
 src/test/regress/sql/cluster.sql              | 11 ++++++++--
 4 files changed, 28 insertions(+), 5 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index b17edd771e2..e4a4860805b 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -214,7 +214,6 @@ repack_setup_logical_decoding(Oid relid)
 	/*
 	 * Make sure we can use logical decoding.
 	 */
-	CheckSlotPermissions();
 	CheckLogicalDecodingRequirements(true);
 
 	/*
diff --git a/src/test/modules/test_plan_advice/t/001_replan_regress.pl b/src/test/modules/test_plan_advice/t/001_replan_regress.pl
index 38ffa4d11ae..452b179a665 100644
--- a/src/test/modules/test_plan_advice/t/001_replan_regress.pl
+++ b/src/test/modules/test_plan_advice/t/001_replan_regress.pl
@@ -18,6 +18,7 @@ $node->init();
 # Set up our desired configuration.
 $node->append_conf('postgresql.conf', <<EOM);
 shared_preload_libraries='test_plan_advice'
+wal_level=replica
 pg_plan_advice.always_explain_supplied_advice=false
 pg_plan_advice.feedback_warnings=true
 EOM
diff --git a/src/test/regress/expected/cluster.out b/src/test/regress/expected/cluster.out
index 6127b215a86..e17bc91fae1 100644
--- a/src/test/regress/expected/cluster.out
+++ b/src/test/regress/expected/cluster.out
@@ -543,15 +543,17 @@ ERROR:  REPACK (CONCURRENTLY) is not supported for partitioned tables
 HINT:  Consider running the command on individual partitions.
 DROP TABLE clstrpart;
 -- Ownership of partitions is checked
-CREATE TABLE ptnowner(i int unique) PARTITION BY LIST (i);
+CREATE TABLE ptnowner(i int unique not null) PARTITION BY LIST (i);
 CREATE INDEX ptnowner_i_idx ON ptnowner(i);
 CREATE TABLE ptnowner1 PARTITION OF ptnowner FOR VALUES IN (1);
-CREATE ROLE regress_ptnowner;
+CREATE ROLE regress_ptnowner LOGIN;
 CREATE TABLE ptnowner2 PARTITION OF ptnowner FOR VALUES IN (2);
 ALTER TABLE ptnowner1 OWNER TO regress_ptnowner;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
 ERROR:  permission denied for table ptnowner
+ALTER TABLE ptnowner1 REPLICA IDENTITY USING INDEX ptnowner1_i_key;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
 ALTER TABLE ptnowner OWNER TO regress_ptnowner;
 CREATE TEMP TABLE ptnowner_oldnodes AS
@@ -560,6 +562,11 @@ CREATE TEMP TABLE ptnowner_oldnodes AS
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
 WARNING:  permission denied to execute CLUSTER on "ptnowner2", skipping it
+-- still can't repack without a replica identity
+ALTER TABLE ptnowner1 REPLICA IDENTITY DEFAULT;
+REPACK (CONCURRENTLY) ptnowner1;
+ERROR:  cannot process relation "ptnowner1"
+HINT:  Relation "ptnowner1" has no identity index.
 RESET SESSION AUTHORIZATION;
 SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
   JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
@@ -570,6 +577,15 @@ SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
  ptnowner2 | t
 (3 rows)
 
+SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
+  JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
+  relname  | ?column? 
+-----------+----------
+ ptnowner  | t
+ ptnowner1 | f
+ ptnowner2 | t
+(3 rows)
+
 DROP TABLE ptnowner;
 DROP ROLE regress_ptnowner;
 -- Test CLUSTER with external tuplesorting
diff --git a/src/test/regress/sql/cluster.sql b/src/test/regress/sql/cluster.sql
index d14063a9683..1f471a8821a 100644
--- a/src/test/regress/sql/cluster.sql
+++ b/src/test/regress/sql/cluster.sql
@@ -254,14 +254,16 @@ REPACK (CONCURRENTLY) clstrpart;
 DROP TABLE clstrpart;
 
 -- Ownership of partitions is checked
-CREATE TABLE ptnowner(i int unique) PARTITION BY LIST (i);
+CREATE TABLE ptnowner(i int unique not null) PARTITION BY LIST (i);
 CREATE INDEX ptnowner_i_idx ON ptnowner(i);
 CREATE TABLE ptnowner1 PARTITION OF ptnowner FOR VALUES IN (1);
-CREATE ROLE regress_ptnowner;
+CREATE ROLE regress_ptnowner LOGIN;
 CREATE TABLE ptnowner2 PARTITION OF ptnowner FOR VALUES IN (2);
 ALTER TABLE ptnowner1 OWNER TO regress_ptnowner;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
+ALTER TABLE ptnowner1 REPLICA IDENTITY USING INDEX ptnowner1_i_key;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
 ALTER TABLE ptnowner OWNER TO regress_ptnowner;
 CREATE TEMP TABLE ptnowner_oldnodes AS
@@ -269,7 +271,12 @@ CREATE TEMP TABLE ptnowner_oldnodes AS
   JOIN pg_class AS c ON c.oid=tree.relid;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
+-- still can't repack without a replica identity
+ALTER TABLE ptnowner1 REPLICA IDENTITY DEFAULT;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
+SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
+  JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
 SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
   JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
 DROP TABLE ptnowner;
-- 
2.47.3


--m6zy3l65ushq557m
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-REPACK-do-not-require-LOGIN-privileges.patch"



^ permalink  raw  reply  [nested|flat] 58+ messages in thread

* [PATCH 1/2] REPACK: do not require the user to have REPLICATION
@ 2026-04-20 09:38  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 58+ messages in thread

From: Álvaro Herrera @ 2026-04-20 09:38 UTC (permalink / raw)

Although REPACK (CONCURRENTLY) uses replication slots, there is no
concern that the slot will leak data of other users, because the
MAINTAIN privilege on the table is required anyway; requiring
REPLICATION is user-unfriendly without providing any actual protection.

A related aspect is that the REPLICATION attribute is not needed to
prevent REPACK from stealing slots from logical replication, since
commit e76d8c749c31 made REPACK use a separate pool of replication
slots.

Because there are now successful concurrent repack runs in the
regression tests, we're forced to run test_plan_advice under
wal_level=replica.

Author: Antonin Houska <[email protected]>
Reported-by: Justin Pryzby <[email protected]>
Reviewed-by: Chao Li <[email protected]>
Discussion: https://postgr.es/m/aeJHPNmL4vVy3oPw@pryzbyj2023
---
 src/backend/commands/repack_worker.c          |  1 -
 .../test_plan_advice/t/001_replan_regress.pl  |  1 +
 src/test/regress/expected/cluster.out         | 20 +++++++++++++++++--
 src/test/regress/sql/cluster.sql              | 11 ++++++++--
 4 files changed, 28 insertions(+), 5 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index b17edd771e2..e4a4860805b 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -214,7 +214,6 @@ repack_setup_logical_decoding(Oid relid)
 	/*
 	 * Make sure we can use logical decoding.
 	 */
-	CheckSlotPermissions();
 	CheckLogicalDecodingRequirements(true);
 
 	/*
diff --git a/src/test/modules/test_plan_advice/t/001_replan_regress.pl b/src/test/modules/test_plan_advice/t/001_replan_regress.pl
index 38ffa4d11ae..452b179a665 100644
--- a/src/test/modules/test_plan_advice/t/001_replan_regress.pl
+++ b/src/test/modules/test_plan_advice/t/001_replan_regress.pl
@@ -18,6 +18,7 @@ $node->init();
 # Set up our desired configuration.
 $node->append_conf('postgresql.conf', <<EOM);
 shared_preload_libraries='test_plan_advice'
+wal_level=replica
 pg_plan_advice.always_explain_supplied_advice=false
 pg_plan_advice.feedback_warnings=true
 EOM
diff --git a/src/test/regress/expected/cluster.out b/src/test/regress/expected/cluster.out
index 6127b215a86..e17bc91fae1 100644
--- a/src/test/regress/expected/cluster.out
+++ b/src/test/regress/expected/cluster.out
@@ -543,15 +543,17 @@ ERROR:  REPACK (CONCURRENTLY) is not supported for partitioned tables
 HINT:  Consider running the command on individual partitions.
 DROP TABLE clstrpart;
 -- Ownership of partitions is checked
-CREATE TABLE ptnowner(i int unique) PARTITION BY LIST (i);
+CREATE TABLE ptnowner(i int unique not null) PARTITION BY LIST (i);
 CREATE INDEX ptnowner_i_idx ON ptnowner(i);
 CREATE TABLE ptnowner1 PARTITION OF ptnowner FOR VALUES IN (1);
-CREATE ROLE regress_ptnowner;
+CREATE ROLE regress_ptnowner LOGIN;
 CREATE TABLE ptnowner2 PARTITION OF ptnowner FOR VALUES IN (2);
 ALTER TABLE ptnowner1 OWNER TO regress_ptnowner;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
 ERROR:  permission denied for table ptnowner
+ALTER TABLE ptnowner1 REPLICA IDENTITY USING INDEX ptnowner1_i_key;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
 ALTER TABLE ptnowner OWNER TO regress_ptnowner;
 CREATE TEMP TABLE ptnowner_oldnodes AS
@@ -560,6 +562,11 @@ CREATE TEMP TABLE ptnowner_oldnodes AS
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
 WARNING:  permission denied to execute CLUSTER on "ptnowner2", skipping it
+-- still can't repack without a replica identity
+ALTER TABLE ptnowner1 REPLICA IDENTITY DEFAULT;
+REPACK (CONCURRENTLY) ptnowner1;
+ERROR:  cannot process relation "ptnowner1"
+HINT:  Relation "ptnowner1" has no identity index.
 RESET SESSION AUTHORIZATION;
 SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
   JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
@@ -570,6 +577,15 @@ SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
  ptnowner2 | t
 (3 rows)
 
+SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
+  JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
+  relname  | ?column? 
+-----------+----------
+ ptnowner  | t
+ ptnowner1 | f
+ ptnowner2 | t
+(3 rows)
+
 DROP TABLE ptnowner;
 DROP ROLE regress_ptnowner;
 -- Test CLUSTER with external tuplesorting
diff --git a/src/test/regress/sql/cluster.sql b/src/test/regress/sql/cluster.sql
index d14063a9683..1f471a8821a 100644
--- a/src/test/regress/sql/cluster.sql
+++ b/src/test/regress/sql/cluster.sql
@@ -254,14 +254,16 @@ REPACK (CONCURRENTLY) clstrpart;
 DROP TABLE clstrpart;
 
 -- Ownership of partitions is checked
-CREATE TABLE ptnowner(i int unique) PARTITION BY LIST (i);
+CREATE TABLE ptnowner(i int unique not null) PARTITION BY LIST (i);
 CREATE INDEX ptnowner_i_idx ON ptnowner(i);
 CREATE TABLE ptnowner1 PARTITION OF ptnowner FOR VALUES IN (1);
-CREATE ROLE regress_ptnowner;
+CREATE ROLE regress_ptnowner LOGIN;
 CREATE TABLE ptnowner2 PARTITION OF ptnowner FOR VALUES IN (2);
 ALTER TABLE ptnowner1 OWNER TO regress_ptnowner;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
+ALTER TABLE ptnowner1 REPLICA IDENTITY USING INDEX ptnowner1_i_key;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
 ALTER TABLE ptnowner OWNER TO regress_ptnowner;
 CREATE TEMP TABLE ptnowner_oldnodes AS
@@ -269,7 +271,12 @@ CREATE TEMP TABLE ptnowner_oldnodes AS
   JOIN pg_class AS c ON c.oid=tree.relid;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
+-- still can't repack without a replica identity
+ALTER TABLE ptnowner1 REPLICA IDENTITY DEFAULT;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
+SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
+  JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
 SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
   JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
 DROP TABLE ptnowner;
-- 
2.47.3


--m6zy3l65ushq557m
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-REPACK-do-not-require-LOGIN-privileges.patch"



^ permalink  raw  reply  [nested|flat] 58+ messages in thread

* [PATCH 1/2] REPACK: do not require the user to have REPLICATION
@ 2026-04-20 09:38  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 58+ messages in thread

From: Álvaro Herrera @ 2026-04-20 09:38 UTC (permalink / raw)

Although REPACK (CONCURRENTLY) uses replication slots, there is no
concern that the slot will leak data of other users, because the
MAINTAIN privilege on the table is required anyway; requiring
REPLICATION is user-unfriendly without providing any actual protection.

A related aspect is that the REPLICATION attribute is not needed to
prevent REPACK from stealing slots from logical replication, since
commit e76d8c749c31 made REPACK use a separate pool of replication
slots.

Because there are now successful concurrent repack runs in the
regression tests, we're forced to run test_plan_advice under
wal_level=replica.

Author: Antonin Houska <[email protected]>
Reported-by: Justin Pryzby <[email protected]>
Reviewed-by: Chao Li <[email protected]>
Discussion: https://postgr.es/m/aeJHPNmL4vVy3oPw@pryzbyj2023
---
 src/backend/commands/repack_worker.c          |  1 -
 .../test_plan_advice/t/001_replan_regress.pl  |  1 +
 src/test/regress/expected/cluster.out         | 20 +++++++++++++++++--
 src/test/regress/sql/cluster.sql              | 11 ++++++++--
 4 files changed, 28 insertions(+), 5 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index b17edd771e2..e4a4860805b 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -214,7 +214,6 @@ repack_setup_logical_decoding(Oid relid)
 	/*
 	 * Make sure we can use logical decoding.
 	 */
-	CheckSlotPermissions();
 	CheckLogicalDecodingRequirements(true);
 
 	/*
diff --git a/src/test/modules/test_plan_advice/t/001_replan_regress.pl b/src/test/modules/test_plan_advice/t/001_replan_regress.pl
index 38ffa4d11ae..452b179a665 100644
--- a/src/test/modules/test_plan_advice/t/001_replan_regress.pl
+++ b/src/test/modules/test_plan_advice/t/001_replan_regress.pl
@@ -18,6 +18,7 @@ $node->init();
 # Set up our desired configuration.
 $node->append_conf('postgresql.conf', <<EOM);
 shared_preload_libraries='test_plan_advice'
+wal_level=replica
 pg_plan_advice.always_explain_supplied_advice=false
 pg_plan_advice.feedback_warnings=true
 EOM
diff --git a/src/test/regress/expected/cluster.out b/src/test/regress/expected/cluster.out
index 6127b215a86..e17bc91fae1 100644
--- a/src/test/regress/expected/cluster.out
+++ b/src/test/regress/expected/cluster.out
@@ -543,15 +543,17 @@ ERROR:  REPACK (CONCURRENTLY) is not supported for partitioned tables
 HINT:  Consider running the command on individual partitions.
 DROP TABLE clstrpart;
 -- Ownership of partitions is checked
-CREATE TABLE ptnowner(i int unique) PARTITION BY LIST (i);
+CREATE TABLE ptnowner(i int unique not null) PARTITION BY LIST (i);
 CREATE INDEX ptnowner_i_idx ON ptnowner(i);
 CREATE TABLE ptnowner1 PARTITION OF ptnowner FOR VALUES IN (1);
-CREATE ROLE regress_ptnowner;
+CREATE ROLE regress_ptnowner LOGIN;
 CREATE TABLE ptnowner2 PARTITION OF ptnowner FOR VALUES IN (2);
 ALTER TABLE ptnowner1 OWNER TO regress_ptnowner;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
 ERROR:  permission denied for table ptnowner
+ALTER TABLE ptnowner1 REPLICA IDENTITY USING INDEX ptnowner1_i_key;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
 ALTER TABLE ptnowner OWNER TO regress_ptnowner;
 CREATE TEMP TABLE ptnowner_oldnodes AS
@@ -560,6 +562,11 @@ CREATE TEMP TABLE ptnowner_oldnodes AS
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
 WARNING:  permission denied to execute CLUSTER on "ptnowner2", skipping it
+-- still can't repack without a replica identity
+ALTER TABLE ptnowner1 REPLICA IDENTITY DEFAULT;
+REPACK (CONCURRENTLY) ptnowner1;
+ERROR:  cannot process relation "ptnowner1"
+HINT:  Relation "ptnowner1" has no identity index.
 RESET SESSION AUTHORIZATION;
 SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
   JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
@@ -570,6 +577,15 @@ SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
  ptnowner2 | t
 (3 rows)
 
+SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
+  JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
+  relname  | ?column? 
+-----------+----------
+ ptnowner  | t
+ ptnowner1 | f
+ ptnowner2 | t
+(3 rows)
+
 DROP TABLE ptnowner;
 DROP ROLE regress_ptnowner;
 -- Test CLUSTER with external tuplesorting
diff --git a/src/test/regress/sql/cluster.sql b/src/test/regress/sql/cluster.sql
index d14063a9683..1f471a8821a 100644
--- a/src/test/regress/sql/cluster.sql
+++ b/src/test/regress/sql/cluster.sql
@@ -254,14 +254,16 @@ REPACK (CONCURRENTLY) clstrpart;
 DROP TABLE clstrpart;
 
 -- Ownership of partitions is checked
-CREATE TABLE ptnowner(i int unique) PARTITION BY LIST (i);
+CREATE TABLE ptnowner(i int unique not null) PARTITION BY LIST (i);
 CREATE INDEX ptnowner_i_idx ON ptnowner(i);
 CREATE TABLE ptnowner1 PARTITION OF ptnowner FOR VALUES IN (1);
-CREATE ROLE regress_ptnowner;
+CREATE ROLE regress_ptnowner LOGIN;
 CREATE TABLE ptnowner2 PARTITION OF ptnowner FOR VALUES IN (2);
 ALTER TABLE ptnowner1 OWNER TO regress_ptnowner;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
+ALTER TABLE ptnowner1 REPLICA IDENTITY USING INDEX ptnowner1_i_key;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
 ALTER TABLE ptnowner OWNER TO regress_ptnowner;
 CREATE TEMP TABLE ptnowner_oldnodes AS
@@ -269,7 +271,12 @@ CREATE TEMP TABLE ptnowner_oldnodes AS
   JOIN pg_class AS c ON c.oid=tree.relid;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
+-- still can't repack without a replica identity
+ALTER TABLE ptnowner1 REPLICA IDENTITY DEFAULT;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
+SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
+  JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
 SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
   JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
 DROP TABLE ptnowner;
-- 
2.47.3


--m6zy3l65ushq557m
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-REPACK-do-not-require-LOGIN-privileges.patch"



^ permalink  raw  reply  [nested|flat] 58+ messages in thread

* [PATCH 1/2] REPACK: do not require the user to have REPLICATION
@ 2026-04-20 09:38  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 58+ messages in thread

From: Álvaro Herrera @ 2026-04-20 09:38 UTC (permalink / raw)

Although REPACK (CONCURRENTLY) uses replication slots, there is no
concern that the slot will leak data of other users, because the
MAINTAIN privilege on the table is required anyway; requiring
REPLICATION is user-unfriendly without providing any actual protection.

A related aspect is that the REPLICATION attribute is not needed to
prevent REPACK from stealing slots from logical replication, since
commit e76d8c749c31 made REPACK use a separate pool of replication
slots.

Because there are now successful concurrent repack runs in the
regression tests, we're forced to run test_plan_advice under
wal_level=replica.

Author: Antonin Houska <[email protected]>
Reported-by: Justin Pryzby <[email protected]>
Reviewed-by: Chao Li <[email protected]>
Discussion: https://postgr.es/m/aeJHPNmL4vVy3oPw@pryzbyj2023
---
 src/backend/commands/repack_worker.c          |  1 -
 .../test_plan_advice/t/001_replan_regress.pl  |  1 +
 src/test/regress/expected/cluster.out         | 20 +++++++++++++++++--
 src/test/regress/sql/cluster.sql              | 11 ++++++++--
 4 files changed, 28 insertions(+), 5 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index b17edd771e2..e4a4860805b 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -214,7 +214,6 @@ repack_setup_logical_decoding(Oid relid)
 	/*
 	 * Make sure we can use logical decoding.
 	 */
-	CheckSlotPermissions();
 	CheckLogicalDecodingRequirements(true);
 
 	/*
diff --git a/src/test/modules/test_plan_advice/t/001_replan_regress.pl b/src/test/modules/test_plan_advice/t/001_replan_regress.pl
index 38ffa4d11ae..452b179a665 100644
--- a/src/test/modules/test_plan_advice/t/001_replan_regress.pl
+++ b/src/test/modules/test_plan_advice/t/001_replan_regress.pl
@@ -18,6 +18,7 @@ $node->init();
 # Set up our desired configuration.
 $node->append_conf('postgresql.conf', <<EOM);
 shared_preload_libraries='test_plan_advice'
+wal_level=replica
 pg_plan_advice.always_explain_supplied_advice=false
 pg_plan_advice.feedback_warnings=true
 EOM
diff --git a/src/test/regress/expected/cluster.out b/src/test/regress/expected/cluster.out
index 6127b215a86..e17bc91fae1 100644
--- a/src/test/regress/expected/cluster.out
+++ b/src/test/regress/expected/cluster.out
@@ -543,15 +543,17 @@ ERROR:  REPACK (CONCURRENTLY) is not supported for partitioned tables
 HINT:  Consider running the command on individual partitions.
 DROP TABLE clstrpart;
 -- Ownership of partitions is checked
-CREATE TABLE ptnowner(i int unique) PARTITION BY LIST (i);
+CREATE TABLE ptnowner(i int unique not null) PARTITION BY LIST (i);
 CREATE INDEX ptnowner_i_idx ON ptnowner(i);
 CREATE TABLE ptnowner1 PARTITION OF ptnowner FOR VALUES IN (1);
-CREATE ROLE regress_ptnowner;
+CREATE ROLE regress_ptnowner LOGIN;
 CREATE TABLE ptnowner2 PARTITION OF ptnowner FOR VALUES IN (2);
 ALTER TABLE ptnowner1 OWNER TO regress_ptnowner;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
 ERROR:  permission denied for table ptnowner
+ALTER TABLE ptnowner1 REPLICA IDENTITY USING INDEX ptnowner1_i_key;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
 ALTER TABLE ptnowner OWNER TO regress_ptnowner;
 CREATE TEMP TABLE ptnowner_oldnodes AS
@@ -560,6 +562,11 @@ CREATE TEMP TABLE ptnowner_oldnodes AS
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
 WARNING:  permission denied to execute CLUSTER on "ptnowner2", skipping it
+-- still can't repack without a replica identity
+ALTER TABLE ptnowner1 REPLICA IDENTITY DEFAULT;
+REPACK (CONCURRENTLY) ptnowner1;
+ERROR:  cannot process relation "ptnowner1"
+HINT:  Relation "ptnowner1" has no identity index.
 RESET SESSION AUTHORIZATION;
 SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
   JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
@@ -570,6 +577,15 @@ SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
  ptnowner2 | t
 (3 rows)
 
+SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
+  JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
+  relname  | ?column? 
+-----------+----------
+ ptnowner  | t
+ ptnowner1 | f
+ ptnowner2 | t
+(3 rows)
+
 DROP TABLE ptnowner;
 DROP ROLE regress_ptnowner;
 -- Test CLUSTER with external tuplesorting
diff --git a/src/test/regress/sql/cluster.sql b/src/test/regress/sql/cluster.sql
index d14063a9683..1f471a8821a 100644
--- a/src/test/regress/sql/cluster.sql
+++ b/src/test/regress/sql/cluster.sql
@@ -254,14 +254,16 @@ REPACK (CONCURRENTLY) clstrpart;
 DROP TABLE clstrpart;
 
 -- Ownership of partitions is checked
-CREATE TABLE ptnowner(i int unique) PARTITION BY LIST (i);
+CREATE TABLE ptnowner(i int unique not null) PARTITION BY LIST (i);
 CREATE INDEX ptnowner_i_idx ON ptnowner(i);
 CREATE TABLE ptnowner1 PARTITION OF ptnowner FOR VALUES IN (1);
-CREATE ROLE regress_ptnowner;
+CREATE ROLE regress_ptnowner LOGIN;
 CREATE TABLE ptnowner2 PARTITION OF ptnowner FOR VALUES IN (2);
 ALTER TABLE ptnowner1 OWNER TO regress_ptnowner;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
+ALTER TABLE ptnowner1 REPLICA IDENTITY USING INDEX ptnowner1_i_key;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
 ALTER TABLE ptnowner OWNER TO regress_ptnowner;
 CREATE TEMP TABLE ptnowner_oldnodes AS
@@ -269,7 +271,12 @@ CREATE TEMP TABLE ptnowner_oldnodes AS
   JOIN pg_class AS c ON c.oid=tree.relid;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
+-- still can't repack without a replica identity
+ALTER TABLE ptnowner1 REPLICA IDENTITY DEFAULT;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
+SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
+  JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
 SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
   JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
 DROP TABLE ptnowner;
-- 
2.47.3


--m6zy3l65ushq557m
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-REPACK-do-not-require-LOGIN-privileges.patch"



^ permalink  raw  reply  [nested|flat] 58+ messages in thread

* [PATCH 1/2] REPACK: do not require the user to have REPLICATION
@ 2026-04-20 09:38  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 58+ messages in thread

From: Álvaro Herrera @ 2026-04-20 09:38 UTC (permalink / raw)

Although REPACK (CONCURRENTLY) uses replication slots, there is no
concern that the slot will leak data of other users, because the
MAINTAIN privilege on the table is required anyway; requiring
REPLICATION is user-unfriendly without providing any actual protection.

A related aspect is that the REPLICATION attribute is not needed to
prevent REPACK from stealing slots from logical replication, since
commit e76d8c749c31 made REPACK use a separate pool of replication
slots.

Because there are now successful concurrent repack runs in the
regression tests, we're forced to run test_plan_advice under
wal_level=replica.

Author: Antonin Houska <[email protected]>
Reported-by: Justin Pryzby <[email protected]>
Reviewed-by: Chao Li <[email protected]>
Discussion: https://postgr.es/m/aeJHPNmL4vVy3oPw@pryzbyj2023
---
 src/backend/commands/repack_worker.c          |  1 -
 .../test_plan_advice/t/001_replan_regress.pl  |  1 +
 src/test/regress/expected/cluster.out         | 20 +++++++++++++++++--
 src/test/regress/sql/cluster.sql              | 11 ++++++++--
 4 files changed, 28 insertions(+), 5 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index b17edd771e2..e4a4860805b 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -214,7 +214,6 @@ repack_setup_logical_decoding(Oid relid)
 	/*
 	 * Make sure we can use logical decoding.
 	 */
-	CheckSlotPermissions();
 	CheckLogicalDecodingRequirements(true);
 
 	/*
diff --git a/src/test/modules/test_plan_advice/t/001_replan_regress.pl b/src/test/modules/test_plan_advice/t/001_replan_regress.pl
index 38ffa4d11ae..452b179a665 100644
--- a/src/test/modules/test_plan_advice/t/001_replan_regress.pl
+++ b/src/test/modules/test_plan_advice/t/001_replan_regress.pl
@@ -18,6 +18,7 @@ $node->init();
 # Set up our desired configuration.
 $node->append_conf('postgresql.conf', <<EOM);
 shared_preload_libraries='test_plan_advice'
+wal_level=replica
 pg_plan_advice.always_explain_supplied_advice=false
 pg_plan_advice.feedback_warnings=true
 EOM
diff --git a/src/test/regress/expected/cluster.out b/src/test/regress/expected/cluster.out
index 6127b215a86..e17bc91fae1 100644
--- a/src/test/regress/expected/cluster.out
+++ b/src/test/regress/expected/cluster.out
@@ -543,15 +543,17 @@ ERROR:  REPACK (CONCURRENTLY) is not supported for partitioned tables
 HINT:  Consider running the command on individual partitions.
 DROP TABLE clstrpart;
 -- Ownership of partitions is checked
-CREATE TABLE ptnowner(i int unique) PARTITION BY LIST (i);
+CREATE TABLE ptnowner(i int unique not null) PARTITION BY LIST (i);
 CREATE INDEX ptnowner_i_idx ON ptnowner(i);
 CREATE TABLE ptnowner1 PARTITION OF ptnowner FOR VALUES IN (1);
-CREATE ROLE regress_ptnowner;
+CREATE ROLE regress_ptnowner LOGIN;
 CREATE TABLE ptnowner2 PARTITION OF ptnowner FOR VALUES IN (2);
 ALTER TABLE ptnowner1 OWNER TO regress_ptnowner;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
 ERROR:  permission denied for table ptnowner
+ALTER TABLE ptnowner1 REPLICA IDENTITY USING INDEX ptnowner1_i_key;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
 ALTER TABLE ptnowner OWNER TO regress_ptnowner;
 CREATE TEMP TABLE ptnowner_oldnodes AS
@@ -560,6 +562,11 @@ CREATE TEMP TABLE ptnowner_oldnodes AS
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
 WARNING:  permission denied to execute CLUSTER on "ptnowner2", skipping it
+-- still can't repack without a replica identity
+ALTER TABLE ptnowner1 REPLICA IDENTITY DEFAULT;
+REPACK (CONCURRENTLY) ptnowner1;
+ERROR:  cannot process relation "ptnowner1"
+HINT:  Relation "ptnowner1" has no identity index.
 RESET SESSION AUTHORIZATION;
 SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
   JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
@@ -570,6 +577,15 @@ SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
  ptnowner2 | t
 (3 rows)
 
+SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
+  JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
+  relname  | ?column? 
+-----------+----------
+ ptnowner  | t
+ ptnowner1 | f
+ ptnowner2 | t
+(3 rows)
+
 DROP TABLE ptnowner;
 DROP ROLE regress_ptnowner;
 -- Test CLUSTER with external tuplesorting
diff --git a/src/test/regress/sql/cluster.sql b/src/test/regress/sql/cluster.sql
index d14063a9683..1f471a8821a 100644
--- a/src/test/regress/sql/cluster.sql
+++ b/src/test/regress/sql/cluster.sql
@@ -254,14 +254,16 @@ REPACK (CONCURRENTLY) clstrpart;
 DROP TABLE clstrpart;
 
 -- Ownership of partitions is checked
-CREATE TABLE ptnowner(i int unique) PARTITION BY LIST (i);
+CREATE TABLE ptnowner(i int unique not null) PARTITION BY LIST (i);
 CREATE INDEX ptnowner_i_idx ON ptnowner(i);
 CREATE TABLE ptnowner1 PARTITION OF ptnowner FOR VALUES IN (1);
-CREATE ROLE regress_ptnowner;
+CREATE ROLE regress_ptnowner LOGIN;
 CREATE TABLE ptnowner2 PARTITION OF ptnowner FOR VALUES IN (2);
 ALTER TABLE ptnowner1 OWNER TO regress_ptnowner;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
+ALTER TABLE ptnowner1 REPLICA IDENTITY USING INDEX ptnowner1_i_key;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
 ALTER TABLE ptnowner OWNER TO regress_ptnowner;
 CREATE TEMP TABLE ptnowner_oldnodes AS
@@ -269,7 +271,12 @@ CREATE TEMP TABLE ptnowner_oldnodes AS
   JOIN pg_class AS c ON c.oid=tree.relid;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
+-- still can't repack without a replica identity
+ALTER TABLE ptnowner1 REPLICA IDENTITY DEFAULT;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
+SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
+  JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
 SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
   JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
 DROP TABLE ptnowner;
-- 
2.47.3


--m6zy3l65ushq557m
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-REPACK-do-not-require-LOGIN-privileges.patch"



^ permalink  raw  reply  [nested|flat] 58+ messages in thread

* [PATCH 1/2] REPACK: do not require the user to have REPLICATION
@ 2026-04-20 09:38  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 58+ messages in thread

From: Álvaro Herrera @ 2026-04-20 09:38 UTC (permalink / raw)

Although REPACK (CONCURRENTLY) uses replication slots, there is no
concern that the slot will leak data of other users, because the
MAINTAIN privilege on the table is required anyway; requiring
REPLICATION is user-unfriendly without providing any actual protection.

A related aspect is that the REPLICATION attribute is not needed to
prevent REPACK from stealing slots from logical replication, since
commit e76d8c749c31 made REPACK use a separate pool of replication
slots.

Because there are now successful concurrent repack runs in the
regression tests, we're forced to run test_plan_advice under
wal_level=replica.

Author: Antonin Houska <[email protected]>
Reported-by: Justin Pryzby <[email protected]>
Reviewed-by: Chao Li <[email protected]>
Discussion: https://postgr.es/m/aeJHPNmL4vVy3oPw@pryzbyj2023
---
 src/backend/commands/repack_worker.c          |  1 -
 .../test_plan_advice/t/001_replan_regress.pl  |  1 +
 src/test/regress/expected/cluster.out         | 20 +++++++++++++++++--
 src/test/regress/sql/cluster.sql              | 11 ++++++++--
 4 files changed, 28 insertions(+), 5 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index b17edd771e2..e4a4860805b 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -214,7 +214,6 @@ repack_setup_logical_decoding(Oid relid)
 	/*
 	 * Make sure we can use logical decoding.
 	 */
-	CheckSlotPermissions();
 	CheckLogicalDecodingRequirements(true);
 
 	/*
diff --git a/src/test/modules/test_plan_advice/t/001_replan_regress.pl b/src/test/modules/test_plan_advice/t/001_replan_regress.pl
index 38ffa4d11ae..452b179a665 100644
--- a/src/test/modules/test_plan_advice/t/001_replan_regress.pl
+++ b/src/test/modules/test_plan_advice/t/001_replan_regress.pl
@@ -18,6 +18,7 @@ $node->init();
 # Set up our desired configuration.
 $node->append_conf('postgresql.conf', <<EOM);
 shared_preload_libraries='test_plan_advice'
+wal_level=replica
 pg_plan_advice.always_explain_supplied_advice=false
 pg_plan_advice.feedback_warnings=true
 EOM
diff --git a/src/test/regress/expected/cluster.out b/src/test/regress/expected/cluster.out
index 6127b215a86..e17bc91fae1 100644
--- a/src/test/regress/expected/cluster.out
+++ b/src/test/regress/expected/cluster.out
@@ -543,15 +543,17 @@ ERROR:  REPACK (CONCURRENTLY) is not supported for partitioned tables
 HINT:  Consider running the command on individual partitions.
 DROP TABLE clstrpart;
 -- Ownership of partitions is checked
-CREATE TABLE ptnowner(i int unique) PARTITION BY LIST (i);
+CREATE TABLE ptnowner(i int unique not null) PARTITION BY LIST (i);
 CREATE INDEX ptnowner_i_idx ON ptnowner(i);
 CREATE TABLE ptnowner1 PARTITION OF ptnowner FOR VALUES IN (1);
-CREATE ROLE regress_ptnowner;
+CREATE ROLE regress_ptnowner LOGIN;
 CREATE TABLE ptnowner2 PARTITION OF ptnowner FOR VALUES IN (2);
 ALTER TABLE ptnowner1 OWNER TO regress_ptnowner;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
 ERROR:  permission denied for table ptnowner
+ALTER TABLE ptnowner1 REPLICA IDENTITY USING INDEX ptnowner1_i_key;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
 ALTER TABLE ptnowner OWNER TO regress_ptnowner;
 CREATE TEMP TABLE ptnowner_oldnodes AS
@@ -560,6 +562,11 @@ CREATE TEMP TABLE ptnowner_oldnodes AS
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
 WARNING:  permission denied to execute CLUSTER on "ptnowner2", skipping it
+-- still can't repack without a replica identity
+ALTER TABLE ptnowner1 REPLICA IDENTITY DEFAULT;
+REPACK (CONCURRENTLY) ptnowner1;
+ERROR:  cannot process relation "ptnowner1"
+HINT:  Relation "ptnowner1" has no identity index.
 RESET SESSION AUTHORIZATION;
 SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
   JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
@@ -570,6 +577,15 @@ SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
  ptnowner2 | t
 (3 rows)
 
+SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
+  JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
+  relname  | ?column? 
+-----------+----------
+ ptnowner  | t
+ ptnowner1 | f
+ ptnowner2 | t
+(3 rows)
+
 DROP TABLE ptnowner;
 DROP ROLE regress_ptnowner;
 -- Test CLUSTER with external tuplesorting
diff --git a/src/test/regress/sql/cluster.sql b/src/test/regress/sql/cluster.sql
index d14063a9683..1f471a8821a 100644
--- a/src/test/regress/sql/cluster.sql
+++ b/src/test/regress/sql/cluster.sql
@@ -254,14 +254,16 @@ REPACK (CONCURRENTLY) clstrpart;
 DROP TABLE clstrpart;
 
 -- Ownership of partitions is checked
-CREATE TABLE ptnowner(i int unique) PARTITION BY LIST (i);
+CREATE TABLE ptnowner(i int unique not null) PARTITION BY LIST (i);
 CREATE INDEX ptnowner_i_idx ON ptnowner(i);
 CREATE TABLE ptnowner1 PARTITION OF ptnowner FOR VALUES IN (1);
-CREATE ROLE regress_ptnowner;
+CREATE ROLE regress_ptnowner LOGIN;
 CREATE TABLE ptnowner2 PARTITION OF ptnowner FOR VALUES IN (2);
 ALTER TABLE ptnowner1 OWNER TO regress_ptnowner;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
+ALTER TABLE ptnowner1 REPLICA IDENTITY USING INDEX ptnowner1_i_key;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
 ALTER TABLE ptnowner OWNER TO regress_ptnowner;
 CREATE TEMP TABLE ptnowner_oldnodes AS
@@ -269,7 +271,12 @@ CREATE TEMP TABLE ptnowner_oldnodes AS
   JOIN pg_class AS c ON c.oid=tree.relid;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
+-- still can't repack without a replica identity
+ALTER TABLE ptnowner1 REPLICA IDENTITY DEFAULT;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
+SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
+  JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
 SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
   JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
 DROP TABLE ptnowner;
-- 
2.47.3


--m6zy3l65ushq557m
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-REPACK-do-not-require-LOGIN-privileges.patch"



^ permalink  raw  reply  [nested|flat] 58+ messages in thread

* [PATCH 1/2] REPACK: do not require the user to have REPLICATION
@ 2026-04-20 09:38  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 58+ messages in thread

From: Álvaro Herrera @ 2026-04-20 09:38 UTC (permalink / raw)

Although REPACK (CONCURRENTLY) uses replication slots, there is no
concern that the slot will leak data of other users, because the
MAINTAIN privilege on the table is required anyway; requiring
REPLICATION is user-unfriendly without providing any actual protection.

A related aspect is that the REPLICATION attribute is not needed to
prevent REPACK from stealing slots from logical replication, since
commit e76d8c749c31 made REPACK use a separate pool of replication
slots.

Because there are now successful concurrent repack runs in the
regression tests, we're forced to run test_plan_advice under
wal_level=replica.

Author: Antonin Houska <[email protected]>
Reported-by: Justin Pryzby <[email protected]>
Reviewed-by: Chao Li <[email protected]>
Discussion: https://postgr.es/m/aeJHPNmL4vVy3oPw@pryzbyj2023
---
 src/backend/commands/repack_worker.c          |  1 -
 .../test_plan_advice/t/001_replan_regress.pl  |  1 +
 src/test/regress/expected/cluster.out         | 20 +++++++++++++++++--
 src/test/regress/sql/cluster.sql              | 11 ++++++++--
 4 files changed, 28 insertions(+), 5 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index b17edd771e2..e4a4860805b 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -214,7 +214,6 @@ repack_setup_logical_decoding(Oid relid)
 	/*
 	 * Make sure we can use logical decoding.
 	 */
-	CheckSlotPermissions();
 	CheckLogicalDecodingRequirements(true);
 
 	/*
diff --git a/src/test/modules/test_plan_advice/t/001_replan_regress.pl b/src/test/modules/test_plan_advice/t/001_replan_regress.pl
index 38ffa4d11ae..452b179a665 100644
--- a/src/test/modules/test_plan_advice/t/001_replan_regress.pl
+++ b/src/test/modules/test_plan_advice/t/001_replan_regress.pl
@@ -18,6 +18,7 @@ $node->init();
 # Set up our desired configuration.
 $node->append_conf('postgresql.conf', <<EOM);
 shared_preload_libraries='test_plan_advice'
+wal_level=replica
 pg_plan_advice.always_explain_supplied_advice=false
 pg_plan_advice.feedback_warnings=true
 EOM
diff --git a/src/test/regress/expected/cluster.out b/src/test/regress/expected/cluster.out
index 6127b215a86..e17bc91fae1 100644
--- a/src/test/regress/expected/cluster.out
+++ b/src/test/regress/expected/cluster.out
@@ -543,15 +543,17 @@ ERROR:  REPACK (CONCURRENTLY) is not supported for partitioned tables
 HINT:  Consider running the command on individual partitions.
 DROP TABLE clstrpart;
 -- Ownership of partitions is checked
-CREATE TABLE ptnowner(i int unique) PARTITION BY LIST (i);
+CREATE TABLE ptnowner(i int unique not null) PARTITION BY LIST (i);
 CREATE INDEX ptnowner_i_idx ON ptnowner(i);
 CREATE TABLE ptnowner1 PARTITION OF ptnowner FOR VALUES IN (1);
-CREATE ROLE regress_ptnowner;
+CREATE ROLE regress_ptnowner LOGIN;
 CREATE TABLE ptnowner2 PARTITION OF ptnowner FOR VALUES IN (2);
 ALTER TABLE ptnowner1 OWNER TO regress_ptnowner;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
 ERROR:  permission denied for table ptnowner
+ALTER TABLE ptnowner1 REPLICA IDENTITY USING INDEX ptnowner1_i_key;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
 ALTER TABLE ptnowner OWNER TO regress_ptnowner;
 CREATE TEMP TABLE ptnowner_oldnodes AS
@@ -560,6 +562,11 @@ CREATE TEMP TABLE ptnowner_oldnodes AS
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
 WARNING:  permission denied to execute CLUSTER on "ptnowner2", skipping it
+-- still can't repack without a replica identity
+ALTER TABLE ptnowner1 REPLICA IDENTITY DEFAULT;
+REPACK (CONCURRENTLY) ptnowner1;
+ERROR:  cannot process relation "ptnowner1"
+HINT:  Relation "ptnowner1" has no identity index.
 RESET SESSION AUTHORIZATION;
 SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
   JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
@@ -570,6 +577,15 @@ SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
  ptnowner2 | t
 (3 rows)
 
+SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
+  JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
+  relname  | ?column? 
+-----------+----------
+ ptnowner  | t
+ ptnowner1 | f
+ ptnowner2 | t
+(3 rows)
+
 DROP TABLE ptnowner;
 DROP ROLE regress_ptnowner;
 -- Test CLUSTER with external tuplesorting
diff --git a/src/test/regress/sql/cluster.sql b/src/test/regress/sql/cluster.sql
index d14063a9683..1f471a8821a 100644
--- a/src/test/regress/sql/cluster.sql
+++ b/src/test/regress/sql/cluster.sql
@@ -254,14 +254,16 @@ REPACK (CONCURRENTLY) clstrpart;
 DROP TABLE clstrpart;
 
 -- Ownership of partitions is checked
-CREATE TABLE ptnowner(i int unique) PARTITION BY LIST (i);
+CREATE TABLE ptnowner(i int unique not null) PARTITION BY LIST (i);
 CREATE INDEX ptnowner_i_idx ON ptnowner(i);
 CREATE TABLE ptnowner1 PARTITION OF ptnowner FOR VALUES IN (1);
-CREATE ROLE regress_ptnowner;
+CREATE ROLE regress_ptnowner LOGIN;
 CREATE TABLE ptnowner2 PARTITION OF ptnowner FOR VALUES IN (2);
 ALTER TABLE ptnowner1 OWNER TO regress_ptnowner;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
+ALTER TABLE ptnowner1 REPLICA IDENTITY USING INDEX ptnowner1_i_key;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
 ALTER TABLE ptnowner OWNER TO regress_ptnowner;
 CREATE TEMP TABLE ptnowner_oldnodes AS
@@ -269,7 +271,12 @@ CREATE TEMP TABLE ptnowner_oldnodes AS
   JOIN pg_class AS c ON c.oid=tree.relid;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
+-- still can't repack without a replica identity
+ALTER TABLE ptnowner1 REPLICA IDENTITY DEFAULT;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
+SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
+  JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
 SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
   JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
 DROP TABLE ptnowner;
-- 
2.47.3


--m6zy3l65ushq557m
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-REPACK-do-not-require-LOGIN-privileges.patch"



^ permalink  raw  reply  [nested|flat] 58+ messages in thread

* [PATCH 1/2] REPACK: do not require the user to have REPLICATION
@ 2026-04-20 09:38  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 58+ messages in thread

From: Álvaro Herrera @ 2026-04-20 09:38 UTC (permalink / raw)

Although REPACK (CONCURRENTLY) uses replication slots, there is no
concern that the slot will leak data of other users, because the
MAINTAIN privilege on the table is required anyway; requiring
REPLICATION is user-unfriendly without providing any actual protection.

A related aspect is that the REPLICATION attribute is not needed to
prevent REPACK from stealing slots from logical replication, since
commit e76d8c749c31 made REPACK use a separate pool of replication
slots.

Because there are now successful concurrent repack runs in the
regression tests, we're forced to run test_plan_advice under
wal_level=replica.

Author: Antonin Houska <[email protected]>
Reported-by: Justin Pryzby <[email protected]>
Reviewed-by: Chao Li <[email protected]>
Discussion: https://postgr.es/m/aeJHPNmL4vVy3oPw@pryzbyj2023
---
 src/backend/commands/repack_worker.c          |  1 -
 .../test_plan_advice/t/001_replan_regress.pl  |  1 +
 src/test/regress/expected/cluster.out         | 20 +++++++++++++++++--
 src/test/regress/sql/cluster.sql              | 11 ++++++++--
 4 files changed, 28 insertions(+), 5 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index b17edd771e2..e4a4860805b 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -214,7 +214,6 @@ repack_setup_logical_decoding(Oid relid)
 	/*
 	 * Make sure we can use logical decoding.
 	 */
-	CheckSlotPermissions();
 	CheckLogicalDecodingRequirements(true);
 
 	/*
diff --git a/src/test/modules/test_plan_advice/t/001_replan_regress.pl b/src/test/modules/test_plan_advice/t/001_replan_regress.pl
index 38ffa4d11ae..452b179a665 100644
--- a/src/test/modules/test_plan_advice/t/001_replan_regress.pl
+++ b/src/test/modules/test_plan_advice/t/001_replan_regress.pl
@@ -18,6 +18,7 @@ $node->init();
 # Set up our desired configuration.
 $node->append_conf('postgresql.conf', <<EOM);
 shared_preload_libraries='test_plan_advice'
+wal_level=replica
 pg_plan_advice.always_explain_supplied_advice=false
 pg_plan_advice.feedback_warnings=true
 EOM
diff --git a/src/test/regress/expected/cluster.out b/src/test/regress/expected/cluster.out
index 6127b215a86..e17bc91fae1 100644
--- a/src/test/regress/expected/cluster.out
+++ b/src/test/regress/expected/cluster.out
@@ -543,15 +543,17 @@ ERROR:  REPACK (CONCURRENTLY) is not supported for partitioned tables
 HINT:  Consider running the command on individual partitions.
 DROP TABLE clstrpart;
 -- Ownership of partitions is checked
-CREATE TABLE ptnowner(i int unique) PARTITION BY LIST (i);
+CREATE TABLE ptnowner(i int unique not null) PARTITION BY LIST (i);
 CREATE INDEX ptnowner_i_idx ON ptnowner(i);
 CREATE TABLE ptnowner1 PARTITION OF ptnowner FOR VALUES IN (1);
-CREATE ROLE regress_ptnowner;
+CREATE ROLE regress_ptnowner LOGIN;
 CREATE TABLE ptnowner2 PARTITION OF ptnowner FOR VALUES IN (2);
 ALTER TABLE ptnowner1 OWNER TO regress_ptnowner;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
 ERROR:  permission denied for table ptnowner
+ALTER TABLE ptnowner1 REPLICA IDENTITY USING INDEX ptnowner1_i_key;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
 ALTER TABLE ptnowner OWNER TO regress_ptnowner;
 CREATE TEMP TABLE ptnowner_oldnodes AS
@@ -560,6 +562,11 @@ CREATE TEMP TABLE ptnowner_oldnodes AS
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
 WARNING:  permission denied to execute CLUSTER on "ptnowner2", skipping it
+-- still can't repack without a replica identity
+ALTER TABLE ptnowner1 REPLICA IDENTITY DEFAULT;
+REPACK (CONCURRENTLY) ptnowner1;
+ERROR:  cannot process relation "ptnowner1"
+HINT:  Relation "ptnowner1" has no identity index.
 RESET SESSION AUTHORIZATION;
 SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
   JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
@@ -570,6 +577,15 @@ SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
  ptnowner2 | t
 (3 rows)
 
+SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
+  JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
+  relname  | ?column? 
+-----------+----------
+ ptnowner  | t
+ ptnowner1 | f
+ ptnowner2 | t
+(3 rows)
+
 DROP TABLE ptnowner;
 DROP ROLE regress_ptnowner;
 -- Test CLUSTER with external tuplesorting
diff --git a/src/test/regress/sql/cluster.sql b/src/test/regress/sql/cluster.sql
index d14063a9683..1f471a8821a 100644
--- a/src/test/regress/sql/cluster.sql
+++ b/src/test/regress/sql/cluster.sql
@@ -254,14 +254,16 @@ REPACK (CONCURRENTLY) clstrpart;
 DROP TABLE clstrpart;
 
 -- Ownership of partitions is checked
-CREATE TABLE ptnowner(i int unique) PARTITION BY LIST (i);
+CREATE TABLE ptnowner(i int unique not null) PARTITION BY LIST (i);
 CREATE INDEX ptnowner_i_idx ON ptnowner(i);
 CREATE TABLE ptnowner1 PARTITION OF ptnowner FOR VALUES IN (1);
-CREATE ROLE regress_ptnowner;
+CREATE ROLE regress_ptnowner LOGIN;
 CREATE TABLE ptnowner2 PARTITION OF ptnowner FOR VALUES IN (2);
 ALTER TABLE ptnowner1 OWNER TO regress_ptnowner;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
+ALTER TABLE ptnowner1 REPLICA IDENTITY USING INDEX ptnowner1_i_key;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
 ALTER TABLE ptnowner OWNER TO regress_ptnowner;
 CREATE TEMP TABLE ptnowner_oldnodes AS
@@ -269,7 +271,12 @@ CREATE TEMP TABLE ptnowner_oldnodes AS
   JOIN pg_class AS c ON c.oid=tree.relid;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
+-- still can't repack without a replica identity
+ALTER TABLE ptnowner1 REPLICA IDENTITY DEFAULT;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
+SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
+  JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
 SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
   JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
 DROP TABLE ptnowner;
-- 
2.47.3


--m6zy3l65ushq557m
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-REPACK-do-not-require-LOGIN-privileges.patch"



^ permalink  raw  reply  [nested|flat] 58+ messages in thread

* [PATCH 1/2] REPACK: do not require the user to have REPLICATION
@ 2026-04-20 09:38  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 58+ messages in thread

From: Álvaro Herrera @ 2026-04-20 09:38 UTC (permalink / raw)

Although REPACK (CONCURRENTLY) uses replication slots, there is no
concern that the slot will leak data of other users, because the
MAINTAIN privilege on the table is required anyway; requiring
REPLICATION is user-unfriendly without providing any actual protection.

A related aspect is that the REPLICATION attribute is not needed to
prevent REPACK from stealing slots from logical replication, since
commit e76d8c749c31 made REPACK use a separate pool of replication
slots.

Because there are now successful concurrent repack runs in the
regression tests, we're forced to run test_plan_advice under
wal_level=replica.

Author: Antonin Houska <[email protected]>
Reported-by: Justin Pryzby <[email protected]>
Reviewed-by: Chao Li <[email protected]>
Discussion: https://postgr.es/m/aeJHPNmL4vVy3oPw@pryzbyj2023
---
 src/backend/commands/repack_worker.c          |  1 -
 .../test_plan_advice/t/001_replan_regress.pl  |  1 +
 src/test/regress/expected/cluster.out         | 20 +++++++++++++++++--
 src/test/regress/sql/cluster.sql              | 11 ++++++++--
 4 files changed, 28 insertions(+), 5 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index b17edd771e2..e4a4860805b 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -214,7 +214,6 @@ repack_setup_logical_decoding(Oid relid)
 	/*
 	 * Make sure we can use logical decoding.
 	 */
-	CheckSlotPermissions();
 	CheckLogicalDecodingRequirements(true);
 
 	/*
diff --git a/src/test/modules/test_plan_advice/t/001_replan_regress.pl b/src/test/modules/test_plan_advice/t/001_replan_regress.pl
index 38ffa4d11ae..452b179a665 100644
--- a/src/test/modules/test_plan_advice/t/001_replan_regress.pl
+++ b/src/test/modules/test_plan_advice/t/001_replan_regress.pl
@@ -18,6 +18,7 @@ $node->init();
 # Set up our desired configuration.
 $node->append_conf('postgresql.conf', <<EOM);
 shared_preload_libraries='test_plan_advice'
+wal_level=replica
 pg_plan_advice.always_explain_supplied_advice=false
 pg_plan_advice.feedback_warnings=true
 EOM
diff --git a/src/test/regress/expected/cluster.out b/src/test/regress/expected/cluster.out
index 6127b215a86..e17bc91fae1 100644
--- a/src/test/regress/expected/cluster.out
+++ b/src/test/regress/expected/cluster.out
@@ -543,15 +543,17 @@ ERROR:  REPACK (CONCURRENTLY) is not supported for partitioned tables
 HINT:  Consider running the command on individual partitions.
 DROP TABLE clstrpart;
 -- Ownership of partitions is checked
-CREATE TABLE ptnowner(i int unique) PARTITION BY LIST (i);
+CREATE TABLE ptnowner(i int unique not null) PARTITION BY LIST (i);
 CREATE INDEX ptnowner_i_idx ON ptnowner(i);
 CREATE TABLE ptnowner1 PARTITION OF ptnowner FOR VALUES IN (1);
-CREATE ROLE regress_ptnowner;
+CREATE ROLE regress_ptnowner LOGIN;
 CREATE TABLE ptnowner2 PARTITION OF ptnowner FOR VALUES IN (2);
 ALTER TABLE ptnowner1 OWNER TO regress_ptnowner;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
 ERROR:  permission denied for table ptnowner
+ALTER TABLE ptnowner1 REPLICA IDENTITY USING INDEX ptnowner1_i_key;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
 ALTER TABLE ptnowner OWNER TO regress_ptnowner;
 CREATE TEMP TABLE ptnowner_oldnodes AS
@@ -560,6 +562,11 @@ CREATE TEMP TABLE ptnowner_oldnodes AS
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
 WARNING:  permission denied to execute CLUSTER on "ptnowner2", skipping it
+-- still can't repack without a replica identity
+ALTER TABLE ptnowner1 REPLICA IDENTITY DEFAULT;
+REPACK (CONCURRENTLY) ptnowner1;
+ERROR:  cannot process relation "ptnowner1"
+HINT:  Relation "ptnowner1" has no identity index.
 RESET SESSION AUTHORIZATION;
 SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
   JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
@@ -570,6 +577,15 @@ SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
  ptnowner2 | t
 (3 rows)
 
+SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
+  JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
+  relname  | ?column? 
+-----------+----------
+ ptnowner  | t
+ ptnowner1 | f
+ ptnowner2 | t
+(3 rows)
+
 DROP TABLE ptnowner;
 DROP ROLE regress_ptnowner;
 -- Test CLUSTER with external tuplesorting
diff --git a/src/test/regress/sql/cluster.sql b/src/test/regress/sql/cluster.sql
index d14063a9683..1f471a8821a 100644
--- a/src/test/regress/sql/cluster.sql
+++ b/src/test/regress/sql/cluster.sql
@@ -254,14 +254,16 @@ REPACK (CONCURRENTLY) clstrpart;
 DROP TABLE clstrpart;
 
 -- Ownership of partitions is checked
-CREATE TABLE ptnowner(i int unique) PARTITION BY LIST (i);
+CREATE TABLE ptnowner(i int unique not null) PARTITION BY LIST (i);
 CREATE INDEX ptnowner_i_idx ON ptnowner(i);
 CREATE TABLE ptnowner1 PARTITION OF ptnowner FOR VALUES IN (1);
-CREATE ROLE regress_ptnowner;
+CREATE ROLE regress_ptnowner LOGIN;
 CREATE TABLE ptnowner2 PARTITION OF ptnowner FOR VALUES IN (2);
 ALTER TABLE ptnowner1 OWNER TO regress_ptnowner;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
+ALTER TABLE ptnowner1 REPLICA IDENTITY USING INDEX ptnowner1_i_key;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
 ALTER TABLE ptnowner OWNER TO regress_ptnowner;
 CREATE TEMP TABLE ptnowner_oldnodes AS
@@ -269,7 +271,12 @@ CREATE TEMP TABLE ptnowner_oldnodes AS
   JOIN pg_class AS c ON c.oid=tree.relid;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
+-- still can't repack without a replica identity
+ALTER TABLE ptnowner1 REPLICA IDENTITY DEFAULT;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
+SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
+  JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
 SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
   JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
 DROP TABLE ptnowner;
-- 
2.47.3


--m6zy3l65ushq557m
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-REPACK-do-not-require-LOGIN-privileges.patch"



^ permalink  raw  reply  [nested|flat] 58+ messages in thread

* [PATCH 1/2] REPACK: do not require the user to have REPLICATION
@ 2026-04-20 09:38  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 58+ messages in thread

From: Álvaro Herrera @ 2026-04-20 09:38 UTC (permalink / raw)

Although REPACK (CONCURRENTLY) uses replication slots, there is no
concern that the slot will leak data of other users, because the
MAINTAIN privilege on the table is required anyway; requiring
REPLICATION is user-unfriendly without providing any actual protection.

A related aspect is that the REPLICATION attribute is not needed to
prevent REPACK from stealing slots from logical replication, since
commit e76d8c749c31 made REPACK use a separate pool of replication
slots.

Because there are now successful concurrent repack runs in the
regression tests, we're forced to run test_plan_advice under
wal_level=replica.

Author: Antonin Houska <[email protected]>
Reported-by: Justin Pryzby <[email protected]>
Reviewed-by: Chao Li <[email protected]>
Discussion: https://postgr.es/m/aeJHPNmL4vVy3oPw@pryzbyj2023
---
 src/backend/commands/repack_worker.c          |  1 -
 .../test_plan_advice/t/001_replan_regress.pl  |  1 +
 src/test/regress/expected/cluster.out         | 20 +++++++++++++++++--
 src/test/regress/sql/cluster.sql              | 11 ++++++++--
 4 files changed, 28 insertions(+), 5 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index b17edd771e2..e4a4860805b 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -214,7 +214,6 @@ repack_setup_logical_decoding(Oid relid)
 	/*
 	 * Make sure we can use logical decoding.
 	 */
-	CheckSlotPermissions();
 	CheckLogicalDecodingRequirements(true);
 
 	/*
diff --git a/src/test/modules/test_plan_advice/t/001_replan_regress.pl b/src/test/modules/test_plan_advice/t/001_replan_regress.pl
index 38ffa4d11ae..452b179a665 100644
--- a/src/test/modules/test_plan_advice/t/001_replan_regress.pl
+++ b/src/test/modules/test_plan_advice/t/001_replan_regress.pl
@@ -18,6 +18,7 @@ $node->init();
 # Set up our desired configuration.
 $node->append_conf('postgresql.conf', <<EOM);
 shared_preload_libraries='test_plan_advice'
+wal_level=replica
 pg_plan_advice.always_explain_supplied_advice=false
 pg_plan_advice.feedback_warnings=true
 EOM
diff --git a/src/test/regress/expected/cluster.out b/src/test/regress/expected/cluster.out
index 6127b215a86..e17bc91fae1 100644
--- a/src/test/regress/expected/cluster.out
+++ b/src/test/regress/expected/cluster.out
@@ -543,15 +543,17 @@ ERROR:  REPACK (CONCURRENTLY) is not supported for partitioned tables
 HINT:  Consider running the command on individual partitions.
 DROP TABLE clstrpart;
 -- Ownership of partitions is checked
-CREATE TABLE ptnowner(i int unique) PARTITION BY LIST (i);
+CREATE TABLE ptnowner(i int unique not null) PARTITION BY LIST (i);
 CREATE INDEX ptnowner_i_idx ON ptnowner(i);
 CREATE TABLE ptnowner1 PARTITION OF ptnowner FOR VALUES IN (1);
-CREATE ROLE regress_ptnowner;
+CREATE ROLE regress_ptnowner LOGIN;
 CREATE TABLE ptnowner2 PARTITION OF ptnowner FOR VALUES IN (2);
 ALTER TABLE ptnowner1 OWNER TO regress_ptnowner;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
 ERROR:  permission denied for table ptnowner
+ALTER TABLE ptnowner1 REPLICA IDENTITY USING INDEX ptnowner1_i_key;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
 ALTER TABLE ptnowner OWNER TO regress_ptnowner;
 CREATE TEMP TABLE ptnowner_oldnodes AS
@@ -560,6 +562,11 @@ CREATE TEMP TABLE ptnowner_oldnodes AS
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
 WARNING:  permission denied to execute CLUSTER on "ptnowner2", skipping it
+-- still can't repack without a replica identity
+ALTER TABLE ptnowner1 REPLICA IDENTITY DEFAULT;
+REPACK (CONCURRENTLY) ptnowner1;
+ERROR:  cannot process relation "ptnowner1"
+HINT:  Relation "ptnowner1" has no identity index.
 RESET SESSION AUTHORIZATION;
 SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
   JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
@@ -570,6 +577,15 @@ SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
  ptnowner2 | t
 (3 rows)
 
+SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
+  JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
+  relname  | ?column? 
+-----------+----------
+ ptnowner  | t
+ ptnowner1 | f
+ ptnowner2 | t
+(3 rows)
+
 DROP TABLE ptnowner;
 DROP ROLE regress_ptnowner;
 -- Test CLUSTER with external tuplesorting
diff --git a/src/test/regress/sql/cluster.sql b/src/test/regress/sql/cluster.sql
index d14063a9683..1f471a8821a 100644
--- a/src/test/regress/sql/cluster.sql
+++ b/src/test/regress/sql/cluster.sql
@@ -254,14 +254,16 @@ REPACK (CONCURRENTLY) clstrpart;
 DROP TABLE clstrpart;
 
 -- Ownership of partitions is checked
-CREATE TABLE ptnowner(i int unique) PARTITION BY LIST (i);
+CREATE TABLE ptnowner(i int unique not null) PARTITION BY LIST (i);
 CREATE INDEX ptnowner_i_idx ON ptnowner(i);
 CREATE TABLE ptnowner1 PARTITION OF ptnowner FOR VALUES IN (1);
-CREATE ROLE regress_ptnowner;
+CREATE ROLE regress_ptnowner LOGIN;
 CREATE TABLE ptnowner2 PARTITION OF ptnowner FOR VALUES IN (2);
 ALTER TABLE ptnowner1 OWNER TO regress_ptnowner;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
+ALTER TABLE ptnowner1 REPLICA IDENTITY USING INDEX ptnowner1_i_key;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
 ALTER TABLE ptnowner OWNER TO regress_ptnowner;
 CREATE TEMP TABLE ptnowner_oldnodes AS
@@ -269,7 +271,12 @@ CREATE TEMP TABLE ptnowner_oldnodes AS
   JOIN pg_class AS c ON c.oid=tree.relid;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
+-- still can't repack without a replica identity
+ALTER TABLE ptnowner1 REPLICA IDENTITY DEFAULT;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
+SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
+  JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
 SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
   JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
 DROP TABLE ptnowner;
-- 
2.47.3


--m6zy3l65ushq557m
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-REPACK-do-not-require-LOGIN-privileges.patch"



^ permalink  raw  reply  [nested|flat] 58+ messages in thread

* [PATCH 1/2] REPACK: do not require the user to have REPLICATION
@ 2026-04-20 09:38  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 58+ messages in thread

From: Álvaro Herrera @ 2026-04-20 09:38 UTC (permalink / raw)

Although REPACK (CONCURRENTLY) uses replication slots, there is no
concern that the slot will leak data of other users, because the
MAINTAIN privilege on the table is required anyway; requiring
REPLICATION is user-unfriendly without providing any actual protection.

A related aspect is that the REPLICATION attribute is not needed to
prevent REPACK from stealing slots from logical replication, since
commit e76d8c749c31 made REPACK use a separate pool of replication
slots.

Because there are now successful concurrent repack runs in the
regression tests, we're forced to run test_plan_advice under
wal_level=replica.

Author: Antonin Houska <[email protected]>
Reported-by: Justin Pryzby <[email protected]>
Reviewed-by: Chao Li <[email protected]>
Discussion: https://postgr.es/m/aeJHPNmL4vVy3oPw@pryzbyj2023
---
 src/backend/commands/repack_worker.c          |  1 -
 .../test_plan_advice/t/001_replan_regress.pl  |  1 +
 src/test/regress/expected/cluster.out         | 20 +++++++++++++++++--
 src/test/regress/sql/cluster.sql              | 11 ++++++++--
 4 files changed, 28 insertions(+), 5 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index b17edd771e2..e4a4860805b 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -214,7 +214,6 @@ repack_setup_logical_decoding(Oid relid)
 	/*
 	 * Make sure we can use logical decoding.
 	 */
-	CheckSlotPermissions();
 	CheckLogicalDecodingRequirements(true);
 
 	/*
diff --git a/src/test/modules/test_plan_advice/t/001_replan_regress.pl b/src/test/modules/test_plan_advice/t/001_replan_regress.pl
index 38ffa4d11ae..452b179a665 100644
--- a/src/test/modules/test_plan_advice/t/001_replan_regress.pl
+++ b/src/test/modules/test_plan_advice/t/001_replan_regress.pl
@@ -18,6 +18,7 @@ $node->init();
 # Set up our desired configuration.
 $node->append_conf('postgresql.conf', <<EOM);
 shared_preload_libraries='test_plan_advice'
+wal_level=replica
 pg_plan_advice.always_explain_supplied_advice=false
 pg_plan_advice.feedback_warnings=true
 EOM
diff --git a/src/test/regress/expected/cluster.out b/src/test/regress/expected/cluster.out
index 6127b215a86..e17bc91fae1 100644
--- a/src/test/regress/expected/cluster.out
+++ b/src/test/regress/expected/cluster.out
@@ -543,15 +543,17 @@ ERROR:  REPACK (CONCURRENTLY) is not supported for partitioned tables
 HINT:  Consider running the command on individual partitions.
 DROP TABLE clstrpart;
 -- Ownership of partitions is checked
-CREATE TABLE ptnowner(i int unique) PARTITION BY LIST (i);
+CREATE TABLE ptnowner(i int unique not null) PARTITION BY LIST (i);
 CREATE INDEX ptnowner_i_idx ON ptnowner(i);
 CREATE TABLE ptnowner1 PARTITION OF ptnowner FOR VALUES IN (1);
-CREATE ROLE regress_ptnowner;
+CREATE ROLE regress_ptnowner LOGIN;
 CREATE TABLE ptnowner2 PARTITION OF ptnowner FOR VALUES IN (2);
 ALTER TABLE ptnowner1 OWNER TO regress_ptnowner;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
 ERROR:  permission denied for table ptnowner
+ALTER TABLE ptnowner1 REPLICA IDENTITY USING INDEX ptnowner1_i_key;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
 ALTER TABLE ptnowner OWNER TO regress_ptnowner;
 CREATE TEMP TABLE ptnowner_oldnodes AS
@@ -560,6 +562,11 @@ CREATE TEMP TABLE ptnowner_oldnodes AS
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
 WARNING:  permission denied to execute CLUSTER on "ptnowner2", skipping it
+-- still can't repack without a replica identity
+ALTER TABLE ptnowner1 REPLICA IDENTITY DEFAULT;
+REPACK (CONCURRENTLY) ptnowner1;
+ERROR:  cannot process relation "ptnowner1"
+HINT:  Relation "ptnowner1" has no identity index.
 RESET SESSION AUTHORIZATION;
 SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
   JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
@@ -570,6 +577,15 @@ SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
  ptnowner2 | t
 (3 rows)
 
+SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
+  JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
+  relname  | ?column? 
+-----------+----------
+ ptnowner  | t
+ ptnowner1 | f
+ ptnowner2 | t
+(3 rows)
+
 DROP TABLE ptnowner;
 DROP ROLE regress_ptnowner;
 -- Test CLUSTER with external tuplesorting
diff --git a/src/test/regress/sql/cluster.sql b/src/test/regress/sql/cluster.sql
index d14063a9683..1f471a8821a 100644
--- a/src/test/regress/sql/cluster.sql
+++ b/src/test/regress/sql/cluster.sql
@@ -254,14 +254,16 @@ REPACK (CONCURRENTLY) clstrpart;
 DROP TABLE clstrpart;
 
 -- Ownership of partitions is checked
-CREATE TABLE ptnowner(i int unique) PARTITION BY LIST (i);
+CREATE TABLE ptnowner(i int unique not null) PARTITION BY LIST (i);
 CREATE INDEX ptnowner_i_idx ON ptnowner(i);
 CREATE TABLE ptnowner1 PARTITION OF ptnowner FOR VALUES IN (1);
-CREATE ROLE regress_ptnowner;
+CREATE ROLE regress_ptnowner LOGIN;
 CREATE TABLE ptnowner2 PARTITION OF ptnowner FOR VALUES IN (2);
 ALTER TABLE ptnowner1 OWNER TO regress_ptnowner;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
+ALTER TABLE ptnowner1 REPLICA IDENTITY USING INDEX ptnowner1_i_key;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
 ALTER TABLE ptnowner OWNER TO regress_ptnowner;
 CREATE TEMP TABLE ptnowner_oldnodes AS
@@ -269,7 +271,12 @@ CREATE TEMP TABLE ptnowner_oldnodes AS
   JOIN pg_class AS c ON c.oid=tree.relid;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
+-- still can't repack without a replica identity
+ALTER TABLE ptnowner1 REPLICA IDENTITY DEFAULT;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
+SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
+  JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
 SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
   JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
 DROP TABLE ptnowner;
-- 
2.47.3


--m6zy3l65ushq557m
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-REPACK-do-not-require-LOGIN-privileges.patch"



^ permalink  raw  reply  [nested|flat] 58+ messages in thread

* [PATCH 1/2] REPACK: do not require the user to have REPLICATION
@ 2026-04-20 09:38  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 58+ messages in thread

From: Álvaro Herrera @ 2026-04-20 09:38 UTC (permalink / raw)

Although REPACK (CONCURRENTLY) uses replication slots, there is no
concern that the slot will leak data of other users, because the
MAINTAIN privilege on the table is required anyway; requiring
REPLICATION is user-unfriendly without providing any actual protection.

A related aspect is that the REPLICATION attribute is not needed to
prevent REPACK from stealing slots from logical replication, since
commit e76d8c749c31 made REPACK use a separate pool of replication
slots.

Because there are now successful concurrent repack runs in the
regression tests, we're forced to run test_plan_advice under
wal_level=replica.

Author: Antonin Houska <[email protected]>
Reported-by: Justin Pryzby <[email protected]>
Reviewed-by: Chao Li <[email protected]>
Discussion: https://postgr.es/m/aeJHPNmL4vVy3oPw@pryzbyj2023
---
 src/backend/commands/repack_worker.c          |  1 -
 .../test_plan_advice/t/001_replan_regress.pl  |  1 +
 src/test/regress/expected/cluster.out         | 20 +++++++++++++++++--
 src/test/regress/sql/cluster.sql              | 11 ++++++++--
 4 files changed, 28 insertions(+), 5 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index b17edd771e2..e4a4860805b 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -214,7 +214,6 @@ repack_setup_logical_decoding(Oid relid)
 	/*
 	 * Make sure we can use logical decoding.
 	 */
-	CheckSlotPermissions();
 	CheckLogicalDecodingRequirements(true);
 
 	/*
diff --git a/src/test/modules/test_plan_advice/t/001_replan_regress.pl b/src/test/modules/test_plan_advice/t/001_replan_regress.pl
index 38ffa4d11ae..452b179a665 100644
--- a/src/test/modules/test_plan_advice/t/001_replan_regress.pl
+++ b/src/test/modules/test_plan_advice/t/001_replan_regress.pl
@@ -18,6 +18,7 @@ $node->init();
 # Set up our desired configuration.
 $node->append_conf('postgresql.conf', <<EOM);
 shared_preload_libraries='test_plan_advice'
+wal_level=replica
 pg_plan_advice.always_explain_supplied_advice=false
 pg_plan_advice.feedback_warnings=true
 EOM
diff --git a/src/test/regress/expected/cluster.out b/src/test/regress/expected/cluster.out
index 6127b215a86..e17bc91fae1 100644
--- a/src/test/regress/expected/cluster.out
+++ b/src/test/regress/expected/cluster.out
@@ -543,15 +543,17 @@ ERROR:  REPACK (CONCURRENTLY) is not supported for partitioned tables
 HINT:  Consider running the command on individual partitions.
 DROP TABLE clstrpart;
 -- Ownership of partitions is checked
-CREATE TABLE ptnowner(i int unique) PARTITION BY LIST (i);
+CREATE TABLE ptnowner(i int unique not null) PARTITION BY LIST (i);
 CREATE INDEX ptnowner_i_idx ON ptnowner(i);
 CREATE TABLE ptnowner1 PARTITION OF ptnowner FOR VALUES IN (1);
-CREATE ROLE regress_ptnowner;
+CREATE ROLE regress_ptnowner LOGIN;
 CREATE TABLE ptnowner2 PARTITION OF ptnowner FOR VALUES IN (2);
 ALTER TABLE ptnowner1 OWNER TO regress_ptnowner;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
 ERROR:  permission denied for table ptnowner
+ALTER TABLE ptnowner1 REPLICA IDENTITY USING INDEX ptnowner1_i_key;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
 ALTER TABLE ptnowner OWNER TO regress_ptnowner;
 CREATE TEMP TABLE ptnowner_oldnodes AS
@@ -560,6 +562,11 @@ CREATE TEMP TABLE ptnowner_oldnodes AS
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
 WARNING:  permission denied to execute CLUSTER on "ptnowner2", skipping it
+-- still can't repack without a replica identity
+ALTER TABLE ptnowner1 REPLICA IDENTITY DEFAULT;
+REPACK (CONCURRENTLY) ptnowner1;
+ERROR:  cannot process relation "ptnowner1"
+HINT:  Relation "ptnowner1" has no identity index.
 RESET SESSION AUTHORIZATION;
 SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
   JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
@@ -570,6 +577,15 @@ SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
  ptnowner2 | t
 (3 rows)
 
+SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
+  JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
+  relname  | ?column? 
+-----------+----------
+ ptnowner  | t
+ ptnowner1 | f
+ ptnowner2 | t
+(3 rows)
+
 DROP TABLE ptnowner;
 DROP ROLE regress_ptnowner;
 -- Test CLUSTER with external tuplesorting
diff --git a/src/test/regress/sql/cluster.sql b/src/test/regress/sql/cluster.sql
index d14063a9683..1f471a8821a 100644
--- a/src/test/regress/sql/cluster.sql
+++ b/src/test/regress/sql/cluster.sql
@@ -254,14 +254,16 @@ REPACK (CONCURRENTLY) clstrpart;
 DROP TABLE clstrpart;
 
 -- Ownership of partitions is checked
-CREATE TABLE ptnowner(i int unique) PARTITION BY LIST (i);
+CREATE TABLE ptnowner(i int unique not null) PARTITION BY LIST (i);
 CREATE INDEX ptnowner_i_idx ON ptnowner(i);
 CREATE TABLE ptnowner1 PARTITION OF ptnowner FOR VALUES IN (1);
-CREATE ROLE regress_ptnowner;
+CREATE ROLE regress_ptnowner LOGIN;
 CREATE TABLE ptnowner2 PARTITION OF ptnowner FOR VALUES IN (2);
 ALTER TABLE ptnowner1 OWNER TO regress_ptnowner;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
+ALTER TABLE ptnowner1 REPLICA IDENTITY USING INDEX ptnowner1_i_key;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
 ALTER TABLE ptnowner OWNER TO regress_ptnowner;
 CREATE TEMP TABLE ptnowner_oldnodes AS
@@ -269,7 +271,12 @@ CREATE TEMP TABLE ptnowner_oldnodes AS
   JOIN pg_class AS c ON c.oid=tree.relid;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
+-- still can't repack without a replica identity
+ALTER TABLE ptnowner1 REPLICA IDENTITY DEFAULT;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
+SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
+  JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
 SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
   JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
 DROP TABLE ptnowner;
-- 
2.47.3


--m6zy3l65ushq557m
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-REPACK-do-not-require-LOGIN-privileges.patch"



^ permalink  raw  reply  [nested|flat] 58+ messages in thread

* [PATCH 1/2] REPACK: do not require the user to have REPLICATION
@ 2026-04-20 09:38  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 58+ messages in thread

From: Álvaro Herrera @ 2026-04-20 09:38 UTC (permalink / raw)

Although REPACK (CONCURRENTLY) uses replication slots, there is no
concern that the slot will leak data of other users, because the
MAINTAIN privilege on the table is required anyway; requiring
REPLICATION is user-unfriendly without providing any actual protection.

A related aspect is that the REPLICATION attribute is not needed to
prevent REPACK from stealing slots from logical replication, since
commit e76d8c749c31 made REPACK use a separate pool of replication
slots.

Because there are now successful concurrent repack runs in the
regression tests, we're forced to run test_plan_advice under
wal_level=replica.

Author: Antonin Houska <[email protected]>
Reported-by: Justin Pryzby <[email protected]>
Reviewed-by: Chao Li <[email protected]>
Discussion: https://postgr.es/m/aeJHPNmL4vVy3oPw@pryzbyj2023
---
 src/backend/commands/repack_worker.c          |  1 -
 .../test_plan_advice/t/001_replan_regress.pl  |  1 +
 src/test/regress/expected/cluster.out         | 20 +++++++++++++++++--
 src/test/regress/sql/cluster.sql              | 11 ++++++++--
 4 files changed, 28 insertions(+), 5 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index b17edd771e2..e4a4860805b 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -214,7 +214,6 @@ repack_setup_logical_decoding(Oid relid)
 	/*
 	 * Make sure we can use logical decoding.
 	 */
-	CheckSlotPermissions();
 	CheckLogicalDecodingRequirements(true);
 
 	/*
diff --git a/src/test/modules/test_plan_advice/t/001_replan_regress.pl b/src/test/modules/test_plan_advice/t/001_replan_regress.pl
index 38ffa4d11ae..452b179a665 100644
--- a/src/test/modules/test_plan_advice/t/001_replan_regress.pl
+++ b/src/test/modules/test_plan_advice/t/001_replan_regress.pl
@@ -18,6 +18,7 @@ $node->init();
 # Set up our desired configuration.
 $node->append_conf('postgresql.conf', <<EOM);
 shared_preload_libraries='test_plan_advice'
+wal_level=replica
 pg_plan_advice.always_explain_supplied_advice=false
 pg_plan_advice.feedback_warnings=true
 EOM
diff --git a/src/test/regress/expected/cluster.out b/src/test/regress/expected/cluster.out
index 6127b215a86..e17bc91fae1 100644
--- a/src/test/regress/expected/cluster.out
+++ b/src/test/regress/expected/cluster.out
@@ -543,15 +543,17 @@ ERROR:  REPACK (CONCURRENTLY) is not supported for partitioned tables
 HINT:  Consider running the command on individual partitions.
 DROP TABLE clstrpart;
 -- Ownership of partitions is checked
-CREATE TABLE ptnowner(i int unique) PARTITION BY LIST (i);
+CREATE TABLE ptnowner(i int unique not null) PARTITION BY LIST (i);
 CREATE INDEX ptnowner_i_idx ON ptnowner(i);
 CREATE TABLE ptnowner1 PARTITION OF ptnowner FOR VALUES IN (1);
-CREATE ROLE regress_ptnowner;
+CREATE ROLE regress_ptnowner LOGIN;
 CREATE TABLE ptnowner2 PARTITION OF ptnowner FOR VALUES IN (2);
 ALTER TABLE ptnowner1 OWNER TO regress_ptnowner;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
 ERROR:  permission denied for table ptnowner
+ALTER TABLE ptnowner1 REPLICA IDENTITY USING INDEX ptnowner1_i_key;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
 ALTER TABLE ptnowner OWNER TO regress_ptnowner;
 CREATE TEMP TABLE ptnowner_oldnodes AS
@@ -560,6 +562,11 @@ CREATE TEMP TABLE ptnowner_oldnodes AS
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
 WARNING:  permission denied to execute CLUSTER on "ptnowner2", skipping it
+-- still can't repack without a replica identity
+ALTER TABLE ptnowner1 REPLICA IDENTITY DEFAULT;
+REPACK (CONCURRENTLY) ptnowner1;
+ERROR:  cannot process relation "ptnowner1"
+HINT:  Relation "ptnowner1" has no identity index.
 RESET SESSION AUTHORIZATION;
 SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
   JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
@@ -570,6 +577,15 @@ SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
  ptnowner2 | t
 (3 rows)
 
+SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
+  JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
+  relname  | ?column? 
+-----------+----------
+ ptnowner  | t
+ ptnowner1 | f
+ ptnowner2 | t
+(3 rows)
+
 DROP TABLE ptnowner;
 DROP ROLE regress_ptnowner;
 -- Test CLUSTER with external tuplesorting
diff --git a/src/test/regress/sql/cluster.sql b/src/test/regress/sql/cluster.sql
index d14063a9683..1f471a8821a 100644
--- a/src/test/regress/sql/cluster.sql
+++ b/src/test/regress/sql/cluster.sql
@@ -254,14 +254,16 @@ REPACK (CONCURRENTLY) clstrpart;
 DROP TABLE clstrpart;
 
 -- Ownership of partitions is checked
-CREATE TABLE ptnowner(i int unique) PARTITION BY LIST (i);
+CREATE TABLE ptnowner(i int unique not null) PARTITION BY LIST (i);
 CREATE INDEX ptnowner_i_idx ON ptnowner(i);
 CREATE TABLE ptnowner1 PARTITION OF ptnowner FOR VALUES IN (1);
-CREATE ROLE regress_ptnowner;
+CREATE ROLE regress_ptnowner LOGIN;
 CREATE TABLE ptnowner2 PARTITION OF ptnowner FOR VALUES IN (2);
 ALTER TABLE ptnowner1 OWNER TO regress_ptnowner;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
+ALTER TABLE ptnowner1 REPLICA IDENTITY USING INDEX ptnowner1_i_key;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
 ALTER TABLE ptnowner OWNER TO regress_ptnowner;
 CREATE TEMP TABLE ptnowner_oldnodes AS
@@ -269,7 +271,12 @@ CREATE TEMP TABLE ptnowner_oldnodes AS
   JOIN pg_class AS c ON c.oid=tree.relid;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
+-- still can't repack without a replica identity
+ALTER TABLE ptnowner1 REPLICA IDENTITY DEFAULT;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
+SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
+  JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
 SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
   JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
 DROP TABLE ptnowner;
-- 
2.47.3


--m6zy3l65ushq557m
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-REPACK-do-not-require-LOGIN-privileges.patch"



^ permalink  raw  reply  [nested|flat] 58+ messages in thread

* [PATCH 1/2] REPACK: do not require the user to have REPLICATION
@ 2026-04-20 09:38  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 58+ messages in thread

From: Álvaro Herrera @ 2026-04-20 09:38 UTC (permalink / raw)

Although REPACK (CONCURRENTLY) uses replication slots, there is no
concern that the slot will leak data of other users, because the
MAINTAIN privilege on the table is required anyway; requiring
REPLICATION is user-unfriendly without providing any actual protection.

A related aspect is that the REPLICATION attribute is not needed to
prevent REPACK from stealing slots from logical replication, since
commit e76d8c749c31 made REPACK use a separate pool of replication
slots.

Because there are now successful concurrent repack runs in the
regression tests, we're forced to run test_plan_advice under
wal_level=replica.

Author: Antonin Houska <[email protected]>
Reported-by: Justin Pryzby <[email protected]>
Reviewed-by: Chao Li <[email protected]>
Discussion: https://postgr.es/m/aeJHPNmL4vVy3oPw@pryzbyj2023
---
 src/backend/commands/repack_worker.c          |  1 -
 .../test_plan_advice/t/001_replan_regress.pl  |  1 +
 src/test/regress/expected/cluster.out         | 20 +++++++++++++++++--
 src/test/regress/sql/cluster.sql              | 11 ++++++++--
 4 files changed, 28 insertions(+), 5 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index b17edd771e2..e4a4860805b 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -214,7 +214,6 @@ repack_setup_logical_decoding(Oid relid)
 	/*
 	 * Make sure we can use logical decoding.
 	 */
-	CheckSlotPermissions();
 	CheckLogicalDecodingRequirements(true);
 
 	/*
diff --git a/src/test/modules/test_plan_advice/t/001_replan_regress.pl b/src/test/modules/test_plan_advice/t/001_replan_regress.pl
index 38ffa4d11ae..452b179a665 100644
--- a/src/test/modules/test_plan_advice/t/001_replan_regress.pl
+++ b/src/test/modules/test_plan_advice/t/001_replan_regress.pl
@@ -18,6 +18,7 @@ $node->init();
 # Set up our desired configuration.
 $node->append_conf('postgresql.conf', <<EOM);
 shared_preload_libraries='test_plan_advice'
+wal_level=replica
 pg_plan_advice.always_explain_supplied_advice=false
 pg_plan_advice.feedback_warnings=true
 EOM
diff --git a/src/test/regress/expected/cluster.out b/src/test/regress/expected/cluster.out
index 6127b215a86..e17bc91fae1 100644
--- a/src/test/regress/expected/cluster.out
+++ b/src/test/regress/expected/cluster.out
@@ -543,15 +543,17 @@ ERROR:  REPACK (CONCURRENTLY) is not supported for partitioned tables
 HINT:  Consider running the command on individual partitions.
 DROP TABLE clstrpart;
 -- Ownership of partitions is checked
-CREATE TABLE ptnowner(i int unique) PARTITION BY LIST (i);
+CREATE TABLE ptnowner(i int unique not null) PARTITION BY LIST (i);
 CREATE INDEX ptnowner_i_idx ON ptnowner(i);
 CREATE TABLE ptnowner1 PARTITION OF ptnowner FOR VALUES IN (1);
-CREATE ROLE regress_ptnowner;
+CREATE ROLE regress_ptnowner LOGIN;
 CREATE TABLE ptnowner2 PARTITION OF ptnowner FOR VALUES IN (2);
 ALTER TABLE ptnowner1 OWNER TO regress_ptnowner;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
 ERROR:  permission denied for table ptnowner
+ALTER TABLE ptnowner1 REPLICA IDENTITY USING INDEX ptnowner1_i_key;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
 ALTER TABLE ptnowner OWNER TO regress_ptnowner;
 CREATE TEMP TABLE ptnowner_oldnodes AS
@@ -560,6 +562,11 @@ CREATE TEMP TABLE ptnowner_oldnodes AS
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
 WARNING:  permission denied to execute CLUSTER on "ptnowner2", skipping it
+-- still can't repack without a replica identity
+ALTER TABLE ptnowner1 REPLICA IDENTITY DEFAULT;
+REPACK (CONCURRENTLY) ptnowner1;
+ERROR:  cannot process relation "ptnowner1"
+HINT:  Relation "ptnowner1" has no identity index.
 RESET SESSION AUTHORIZATION;
 SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
   JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
@@ -570,6 +577,15 @@ SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
  ptnowner2 | t
 (3 rows)
 
+SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
+  JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
+  relname  | ?column? 
+-----------+----------
+ ptnowner  | t
+ ptnowner1 | f
+ ptnowner2 | t
+(3 rows)
+
 DROP TABLE ptnowner;
 DROP ROLE regress_ptnowner;
 -- Test CLUSTER with external tuplesorting
diff --git a/src/test/regress/sql/cluster.sql b/src/test/regress/sql/cluster.sql
index d14063a9683..1f471a8821a 100644
--- a/src/test/regress/sql/cluster.sql
+++ b/src/test/regress/sql/cluster.sql
@@ -254,14 +254,16 @@ REPACK (CONCURRENTLY) clstrpart;
 DROP TABLE clstrpart;
 
 -- Ownership of partitions is checked
-CREATE TABLE ptnowner(i int unique) PARTITION BY LIST (i);
+CREATE TABLE ptnowner(i int unique not null) PARTITION BY LIST (i);
 CREATE INDEX ptnowner_i_idx ON ptnowner(i);
 CREATE TABLE ptnowner1 PARTITION OF ptnowner FOR VALUES IN (1);
-CREATE ROLE regress_ptnowner;
+CREATE ROLE regress_ptnowner LOGIN;
 CREATE TABLE ptnowner2 PARTITION OF ptnowner FOR VALUES IN (2);
 ALTER TABLE ptnowner1 OWNER TO regress_ptnowner;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
+ALTER TABLE ptnowner1 REPLICA IDENTITY USING INDEX ptnowner1_i_key;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
 ALTER TABLE ptnowner OWNER TO regress_ptnowner;
 CREATE TEMP TABLE ptnowner_oldnodes AS
@@ -269,7 +271,12 @@ CREATE TEMP TABLE ptnowner_oldnodes AS
   JOIN pg_class AS c ON c.oid=tree.relid;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
+-- still can't repack without a replica identity
+ALTER TABLE ptnowner1 REPLICA IDENTITY DEFAULT;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
+SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
+  JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
 SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
   JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
 DROP TABLE ptnowner;
-- 
2.47.3


--m6zy3l65ushq557m
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-REPACK-do-not-require-LOGIN-privileges.patch"



^ permalink  raw  reply  [nested|flat] 58+ messages in thread

* [PATCH 1/2] REPACK: do not require the user to have REPLICATION
@ 2026-04-20 09:38  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 58+ messages in thread

From: Álvaro Herrera @ 2026-04-20 09:38 UTC (permalink / raw)

Although REPACK (CONCURRENTLY) uses replication slots, there is no
concern that the slot will leak data of other users, because the
MAINTAIN privilege on the table is required anyway; requiring
REPLICATION is user-unfriendly without providing any actual protection.

A related aspect is that the REPLICATION attribute is not needed to
prevent REPACK from stealing slots from logical replication, since
commit e76d8c749c31 made REPACK use a separate pool of replication
slots.

Because there are now successful concurrent repack runs in the
regression tests, we're forced to run test_plan_advice under
wal_level=replica.

Author: Antonin Houska <[email protected]>
Reported-by: Justin Pryzby <[email protected]>
Reviewed-by: Chao Li <[email protected]>
Discussion: https://postgr.es/m/aeJHPNmL4vVy3oPw@pryzbyj2023
---
 src/backend/commands/repack_worker.c          |  1 -
 .../test_plan_advice/t/001_replan_regress.pl  |  1 +
 src/test/regress/expected/cluster.out         | 20 +++++++++++++++++--
 src/test/regress/sql/cluster.sql              | 11 ++++++++--
 4 files changed, 28 insertions(+), 5 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index b17edd771e2..e4a4860805b 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -214,7 +214,6 @@ repack_setup_logical_decoding(Oid relid)
 	/*
 	 * Make sure we can use logical decoding.
 	 */
-	CheckSlotPermissions();
 	CheckLogicalDecodingRequirements(true);
 
 	/*
diff --git a/src/test/modules/test_plan_advice/t/001_replan_regress.pl b/src/test/modules/test_plan_advice/t/001_replan_regress.pl
index 38ffa4d11ae..452b179a665 100644
--- a/src/test/modules/test_plan_advice/t/001_replan_regress.pl
+++ b/src/test/modules/test_plan_advice/t/001_replan_regress.pl
@@ -18,6 +18,7 @@ $node->init();
 # Set up our desired configuration.
 $node->append_conf('postgresql.conf', <<EOM);
 shared_preload_libraries='test_plan_advice'
+wal_level=replica
 pg_plan_advice.always_explain_supplied_advice=false
 pg_plan_advice.feedback_warnings=true
 EOM
diff --git a/src/test/regress/expected/cluster.out b/src/test/regress/expected/cluster.out
index 6127b215a86..e17bc91fae1 100644
--- a/src/test/regress/expected/cluster.out
+++ b/src/test/regress/expected/cluster.out
@@ -543,15 +543,17 @@ ERROR:  REPACK (CONCURRENTLY) is not supported for partitioned tables
 HINT:  Consider running the command on individual partitions.
 DROP TABLE clstrpart;
 -- Ownership of partitions is checked
-CREATE TABLE ptnowner(i int unique) PARTITION BY LIST (i);
+CREATE TABLE ptnowner(i int unique not null) PARTITION BY LIST (i);
 CREATE INDEX ptnowner_i_idx ON ptnowner(i);
 CREATE TABLE ptnowner1 PARTITION OF ptnowner FOR VALUES IN (1);
-CREATE ROLE regress_ptnowner;
+CREATE ROLE regress_ptnowner LOGIN;
 CREATE TABLE ptnowner2 PARTITION OF ptnowner FOR VALUES IN (2);
 ALTER TABLE ptnowner1 OWNER TO regress_ptnowner;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
 ERROR:  permission denied for table ptnowner
+ALTER TABLE ptnowner1 REPLICA IDENTITY USING INDEX ptnowner1_i_key;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
 ALTER TABLE ptnowner OWNER TO regress_ptnowner;
 CREATE TEMP TABLE ptnowner_oldnodes AS
@@ -560,6 +562,11 @@ CREATE TEMP TABLE ptnowner_oldnodes AS
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
 WARNING:  permission denied to execute CLUSTER on "ptnowner2", skipping it
+-- still can't repack without a replica identity
+ALTER TABLE ptnowner1 REPLICA IDENTITY DEFAULT;
+REPACK (CONCURRENTLY) ptnowner1;
+ERROR:  cannot process relation "ptnowner1"
+HINT:  Relation "ptnowner1" has no identity index.
 RESET SESSION AUTHORIZATION;
 SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
   JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
@@ -570,6 +577,15 @@ SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
  ptnowner2 | t
 (3 rows)
 
+SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
+  JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
+  relname  | ?column? 
+-----------+----------
+ ptnowner  | t
+ ptnowner1 | f
+ ptnowner2 | t
+(3 rows)
+
 DROP TABLE ptnowner;
 DROP ROLE regress_ptnowner;
 -- Test CLUSTER with external tuplesorting
diff --git a/src/test/regress/sql/cluster.sql b/src/test/regress/sql/cluster.sql
index d14063a9683..1f471a8821a 100644
--- a/src/test/regress/sql/cluster.sql
+++ b/src/test/regress/sql/cluster.sql
@@ -254,14 +254,16 @@ REPACK (CONCURRENTLY) clstrpart;
 DROP TABLE clstrpart;
 
 -- Ownership of partitions is checked
-CREATE TABLE ptnowner(i int unique) PARTITION BY LIST (i);
+CREATE TABLE ptnowner(i int unique not null) PARTITION BY LIST (i);
 CREATE INDEX ptnowner_i_idx ON ptnowner(i);
 CREATE TABLE ptnowner1 PARTITION OF ptnowner FOR VALUES IN (1);
-CREATE ROLE regress_ptnowner;
+CREATE ROLE regress_ptnowner LOGIN;
 CREATE TABLE ptnowner2 PARTITION OF ptnowner FOR VALUES IN (2);
 ALTER TABLE ptnowner1 OWNER TO regress_ptnowner;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
+ALTER TABLE ptnowner1 REPLICA IDENTITY USING INDEX ptnowner1_i_key;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
 ALTER TABLE ptnowner OWNER TO regress_ptnowner;
 CREATE TEMP TABLE ptnowner_oldnodes AS
@@ -269,7 +271,12 @@ CREATE TEMP TABLE ptnowner_oldnodes AS
   JOIN pg_class AS c ON c.oid=tree.relid;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
+-- still can't repack without a replica identity
+ALTER TABLE ptnowner1 REPLICA IDENTITY DEFAULT;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
+SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
+  JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
 SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
   JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
 DROP TABLE ptnowner;
-- 
2.47.3


--m6zy3l65ushq557m
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-REPACK-do-not-require-LOGIN-privileges.patch"



^ permalink  raw  reply  [nested|flat] 58+ messages in thread

* [PATCH 1/2] REPACK: do not require the user to have REPLICATION
@ 2026-04-20 09:38  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 58+ messages in thread

From: Álvaro Herrera @ 2026-04-20 09:38 UTC (permalink / raw)

Although REPACK (CONCURRENTLY) uses replication slots, there is no
concern that the slot will leak data of other users, because the
MAINTAIN privilege on the table is required anyway; requiring
REPLICATION is user-unfriendly without providing any actual protection.

A related aspect is that the REPLICATION attribute is not needed to
prevent REPACK from stealing slots from logical replication, since
commit e76d8c749c31 made REPACK use a separate pool of replication
slots.

Because there are now successful concurrent repack runs in the
regression tests, we're forced to run test_plan_advice under
wal_level=replica.

Author: Antonin Houska <[email protected]>
Reported-by: Justin Pryzby <[email protected]>
Reviewed-by: Chao Li <[email protected]>
Discussion: https://postgr.es/m/aeJHPNmL4vVy3oPw@pryzbyj2023
---
 src/backend/commands/repack_worker.c          |  1 -
 .../test_plan_advice/t/001_replan_regress.pl  |  1 +
 src/test/regress/expected/cluster.out         | 20 +++++++++++++++++--
 src/test/regress/sql/cluster.sql              | 11 ++++++++--
 4 files changed, 28 insertions(+), 5 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index b17edd771e2..e4a4860805b 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -214,7 +214,6 @@ repack_setup_logical_decoding(Oid relid)
 	/*
 	 * Make sure we can use logical decoding.
 	 */
-	CheckSlotPermissions();
 	CheckLogicalDecodingRequirements(true);
 
 	/*
diff --git a/src/test/modules/test_plan_advice/t/001_replan_regress.pl b/src/test/modules/test_plan_advice/t/001_replan_regress.pl
index 38ffa4d11ae..452b179a665 100644
--- a/src/test/modules/test_plan_advice/t/001_replan_regress.pl
+++ b/src/test/modules/test_plan_advice/t/001_replan_regress.pl
@@ -18,6 +18,7 @@ $node->init();
 # Set up our desired configuration.
 $node->append_conf('postgresql.conf', <<EOM);
 shared_preload_libraries='test_plan_advice'
+wal_level=replica
 pg_plan_advice.always_explain_supplied_advice=false
 pg_plan_advice.feedback_warnings=true
 EOM
diff --git a/src/test/regress/expected/cluster.out b/src/test/regress/expected/cluster.out
index 6127b215a86..e17bc91fae1 100644
--- a/src/test/regress/expected/cluster.out
+++ b/src/test/regress/expected/cluster.out
@@ -543,15 +543,17 @@ ERROR:  REPACK (CONCURRENTLY) is not supported for partitioned tables
 HINT:  Consider running the command on individual partitions.
 DROP TABLE clstrpart;
 -- Ownership of partitions is checked
-CREATE TABLE ptnowner(i int unique) PARTITION BY LIST (i);
+CREATE TABLE ptnowner(i int unique not null) PARTITION BY LIST (i);
 CREATE INDEX ptnowner_i_idx ON ptnowner(i);
 CREATE TABLE ptnowner1 PARTITION OF ptnowner FOR VALUES IN (1);
-CREATE ROLE regress_ptnowner;
+CREATE ROLE regress_ptnowner LOGIN;
 CREATE TABLE ptnowner2 PARTITION OF ptnowner FOR VALUES IN (2);
 ALTER TABLE ptnowner1 OWNER TO regress_ptnowner;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
 ERROR:  permission denied for table ptnowner
+ALTER TABLE ptnowner1 REPLICA IDENTITY USING INDEX ptnowner1_i_key;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
 ALTER TABLE ptnowner OWNER TO regress_ptnowner;
 CREATE TEMP TABLE ptnowner_oldnodes AS
@@ -560,6 +562,11 @@ CREATE TEMP TABLE ptnowner_oldnodes AS
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
 WARNING:  permission denied to execute CLUSTER on "ptnowner2", skipping it
+-- still can't repack without a replica identity
+ALTER TABLE ptnowner1 REPLICA IDENTITY DEFAULT;
+REPACK (CONCURRENTLY) ptnowner1;
+ERROR:  cannot process relation "ptnowner1"
+HINT:  Relation "ptnowner1" has no identity index.
 RESET SESSION AUTHORIZATION;
 SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
   JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
@@ -570,6 +577,15 @@ SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
  ptnowner2 | t
 (3 rows)
 
+SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
+  JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
+  relname  | ?column? 
+-----------+----------
+ ptnowner  | t
+ ptnowner1 | f
+ ptnowner2 | t
+(3 rows)
+
 DROP TABLE ptnowner;
 DROP ROLE regress_ptnowner;
 -- Test CLUSTER with external tuplesorting
diff --git a/src/test/regress/sql/cluster.sql b/src/test/regress/sql/cluster.sql
index d14063a9683..1f471a8821a 100644
--- a/src/test/regress/sql/cluster.sql
+++ b/src/test/regress/sql/cluster.sql
@@ -254,14 +254,16 @@ REPACK (CONCURRENTLY) clstrpart;
 DROP TABLE clstrpart;
 
 -- Ownership of partitions is checked
-CREATE TABLE ptnowner(i int unique) PARTITION BY LIST (i);
+CREATE TABLE ptnowner(i int unique not null) PARTITION BY LIST (i);
 CREATE INDEX ptnowner_i_idx ON ptnowner(i);
 CREATE TABLE ptnowner1 PARTITION OF ptnowner FOR VALUES IN (1);
-CREATE ROLE regress_ptnowner;
+CREATE ROLE regress_ptnowner LOGIN;
 CREATE TABLE ptnowner2 PARTITION OF ptnowner FOR VALUES IN (2);
 ALTER TABLE ptnowner1 OWNER TO regress_ptnowner;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
+ALTER TABLE ptnowner1 REPLICA IDENTITY USING INDEX ptnowner1_i_key;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
 ALTER TABLE ptnowner OWNER TO regress_ptnowner;
 CREATE TEMP TABLE ptnowner_oldnodes AS
@@ -269,7 +271,12 @@ CREATE TEMP TABLE ptnowner_oldnodes AS
   JOIN pg_class AS c ON c.oid=tree.relid;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
+-- still can't repack without a replica identity
+ALTER TABLE ptnowner1 REPLICA IDENTITY DEFAULT;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
+SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
+  JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
 SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
   JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
 DROP TABLE ptnowner;
-- 
2.47.3


--m6zy3l65ushq557m
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-REPACK-do-not-require-LOGIN-privileges.patch"



^ permalink  raw  reply  [nested|flat] 58+ messages in thread

* [PATCH 1/2] REPACK: do not require the user to have REPLICATION
@ 2026-04-20 09:38  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 58+ messages in thread

From: Álvaro Herrera @ 2026-04-20 09:38 UTC (permalink / raw)

Although REPACK (CONCURRENTLY) uses replication slots, there is no
concern that the slot will leak data of other users, because the
MAINTAIN privilege on the table is required anyway; requiring
REPLICATION is user-unfriendly without providing any actual protection.

A related aspect is that the REPLICATION attribute is not needed to
prevent REPACK from stealing slots from logical replication, since
commit e76d8c749c31 made REPACK use a separate pool of replication
slots.

Because there are now successful concurrent repack runs in the
regression tests, we're forced to run test_plan_advice under
wal_level=replica.

Author: Antonin Houska <[email protected]>
Reported-by: Justin Pryzby <[email protected]>
Reviewed-by: Chao Li <[email protected]>
Discussion: https://postgr.es/m/aeJHPNmL4vVy3oPw@pryzbyj2023
---
 src/backend/commands/repack_worker.c          |  1 -
 .../test_plan_advice/t/001_replan_regress.pl  |  1 +
 src/test/regress/expected/cluster.out         | 20 +++++++++++++++++--
 src/test/regress/sql/cluster.sql              | 11 ++++++++--
 4 files changed, 28 insertions(+), 5 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index b17edd771e2..e4a4860805b 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -214,7 +214,6 @@ repack_setup_logical_decoding(Oid relid)
 	/*
 	 * Make sure we can use logical decoding.
 	 */
-	CheckSlotPermissions();
 	CheckLogicalDecodingRequirements(true);
 
 	/*
diff --git a/src/test/modules/test_plan_advice/t/001_replan_regress.pl b/src/test/modules/test_plan_advice/t/001_replan_regress.pl
index 38ffa4d11ae..452b179a665 100644
--- a/src/test/modules/test_plan_advice/t/001_replan_regress.pl
+++ b/src/test/modules/test_plan_advice/t/001_replan_regress.pl
@@ -18,6 +18,7 @@ $node->init();
 # Set up our desired configuration.
 $node->append_conf('postgresql.conf', <<EOM);
 shared_preload_libraries='test_plan_advice'
+wal_level=replica
 pg_plan_advice.always_explain_supplied_advice=false
 pg_plan_advice.feedback_warnings=true
 EOM
diff --git a/src/test/regress/expected/cluster.out b/src/test/regress/expected/cluster.out
index 6127b215a86..e17bc91fae1 100644
--- a/src/test/regress/expected/cluster.out
+++ b/src/test/regress/expected/cluster.out
@@ -543,15 +543,17 @@ ERROR:  REPACK (CONCURRENTLY) is not supported for partitioned tables
 HINT:  Consider running the command on individual partitions.
 DROP TABLE clstrpart;
 -- Ownership of partitions is checked
-CREATE TABLE ptnowner(i int unique) PARTITION BY LIST (i);
+CREATE TABLE ptnowner(i int unique not null) PARTITION BY LIST (i);
 CREATE INDEX ptnowner_i_idx ON ptnowner(i);
 CREATE TABLE ptnowner1 PARTITION OF ptnowner FOR VALUES IN (1);
-CREATE ROLE regress_ptnowner;
+CREATE ROLE regress_ptnowner LOGIN;
 CREATE TABLE ptnowner2 PARTITION OF ptnowner FOR VALUES IN (2);
 ALTER TABLE ptnowner1 OWNER TO regress_ptnowner;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
 ERROR:  permission denied for table ptnowner
+ALTER TABLE ptnowner1 REPLICA IDENTITY USING INDEX ptnowner1_i_key;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
 ALTER TABLE ptnowner OWNER TO regress_ptnowner;
 CREATE TEMP TABLE ptnowner_oldnodes AS
@@ -560,6 +562,11 @@ CREATE TEMP TABLE ptnowner_oldnodes AS
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
 WARNING:  permission denied to execute CLUSTER on "ptnowner2", skipping it
+-- still can't repack without a replica identity
+ALTER TABLE ptnowner1 REPLICA IDENTITY DEFAULT;
+REPACK (CONCURRENTLY) ptnowner1;
+ERROR:  cannot process relation "ptnowner1"
+HINT:  Relation "ptnowner1" has no identity index.
 RESET SESSION AUTHORIZATION;
 SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
   JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
@@ -570,6 +577,15 @@ SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
  ptnowner2 | t
 (3 rows)
 
+SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
+  JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
+  relname  | ?column? 
+-----------+----------
+ ptnowner  | t
+ ptnowner1 | f
+ ptnowner2 | t
+(3 rows)
+
 DROP TABLE ptnowner;
 DROP ROLE regress_ptnowner;
 -- Test CLUSTER with external tuplesorting
diff --git a/src/test/regress/sql/cluster.sql b/src/test/regress/sql/cluster.sql
index d14063a9683..1f471a8821a 100644
--- a/src/test/regress/sql/cluster.sql
+++ b/src/test/regress/sql/cluster.sql
@@ -254,14 +254,16 @@ REPACK (CONCURRENTLY) clstrpart;
 DROP TABLE clstrpart;
 
 -- Ownership of partitions is checked
-CREATE TABLE ptnowner(i int unique) PARTITION BY LIST (i);
+CREATE TABLE ptnowner(i int unique not null) PARTITION BY LIST (i);
 CREATE INDEX ptnowner_i_idx ON ptnowner(i);
 CREATE TABLE ptnowner1 PARTITION OF ptnowner FOR VALUES IN (1);
-CREATE ROLE regress_ptnowner;
+CREATE ROLE regress_ptnowner LOGIN;
 CREATE TABLE ptnowner2 PARTITION OF ptnowner FOR VALUES IN (2);
 ALTER TABLE ptnowner1 OWNER TO regress_ptnowner;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
+ALTER TABLE ptnowner1 REPLICA IDENTITY USING INDEX ptnowner1_i_key;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
 ALTER TABLE ptnowner OWNER TO regress_ptnowner;
 CREATE TEMP TABLE ptnowner_oldnodes AS
@@ -269,7 +271,12 @@ CREATE TEMP TABLE ptnowner_oldnodes AS
   JOIN pg_class AS c ON c.oid=tree.relid;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
+-- still can't repack without a replica identity
+ALTER TABLE ptnowner1 REPLICA IDENTITY DEFAULT;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
+SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
+  JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
 SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
   JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
 DROP TABLE ptnowner;
-- 
2.47.3


--m6zy3l65ushq557m
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-REPACK-do-not-require-LOGIN-privileges.patch"



^ permalink  raw  reply  [nested|flat] 58+ messages in thread

* [PATCH 1/2] REPACK: do not require the user to have REPLICATION
@ 2026-04-20 09:38  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 58+ messages in thread

From: Álvaro Herrera @ 2026-04-20 09:38 UTC (permalink / raw)

Although REPACK (CONCURRENTLY) uses replication slots, there is no
concern that the slot will leak data of other users, because the
MAINTAIN privilege on the table is required anyway; requiring
REPLICATION is user-unfriendly without providing any actual protection.

A related aspect is that the REPLICATION attribute is not needed to
prevent REPACK from stealing slots from logical replication, since
commit e76d8c749c31 made REPACK use a separate pool of replication
slots.

Because there are now successful concurrent repack runs in the
regression tests, we're forced to run test_plan_advice under
wal_level=replica.

Author: Antonin Houska <[email protected]>
Reported-by: Justin Pryzby <[email protected]>
Reviewed-by: Chao Li <[email protected]>
Discussion: https://postgr.es/m/aeJHPNmL4vVy3oPw@pryzbyj2023
---
 src/backend/commands/repack_worker.c          |  1 -
 .../test_plan_advice/t/001_replan_regress.pl  |  1 +
 src/test/regress/expected/cluster.out         | 20 +++++++++++++++++--
 src/test/regress/sql/cluster.sql              | 11 ++++++++--
 4 files changed, 28 insertions(+), 5 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index b17edd771e2..e4a4860805b 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -214,7 +214,6 @@ repack_setup_logical_decoding(Oid relid)
 	/*
 	 * Make sure we can use logical decoding.
 	 */
-	CheckSlotPermissions();
 	CheckLogicalDecodingRequirements(true);
 
 	/*
diff --git a/src/test/modules/test_plan_advice/t/001_replan_regress.pl b/src/test/modules/test_plan_advice/t/001_replan_regress.pl
index 38ffa4d11ae..452b179a665 100644
--- a/src/test/modules/test_plan_advice/t/001_replan_regress.pl
+++ b/src/test/modules/test_plan_advice/t/001_replan_regress.pl
@@ -18,6 +18,7 @@ $node->init();
 # Set up our desired configuration.
 $node->append_conf('postgresql.conf', <<EOM);
 shared_preload_libraries='test_plan_advice'
+wal_level=replica
 pg_plan_advice.always_explain_supplied_advice=false
 pg_plan_advice.feedback_warnings=true
 EOM
diff --git a/src/test/regress/expected/cluster.out b/src/test/regress/expected/cluster.out
index 6127b215a86..e17bc91fae1 100644
--- a/src/test/regress/expected/cluster.out
+++ b/src/test/regress/expected/cluster.out
@@ -543,15 +543,17 @@ ERROR:  REPACK (CONCURRENTLY) is not supported for partitioned tables
 HINT:  Consider running the command on individual partitions.
 DROP TABLE clstrpart;
 -- Ownership of partitions is checked
-CREATE TABLE ptnowner(i int unique) PARTITION BY LIST (i);
+CREATE TABLE ptnowner(i int unique not null) PARTITION BY LIST (i);
 CREATE INDEX ptnowner_i_idx ON ptnowner(i);
 CREATE TABLE ptnowner1 PARTITION OF ptnowner FOR VALUES IN (1);
-CREATE ROLE regress_ptnowner;
+CREATE ROLE regress_ptnowner LOGIN;
 CREATE TABLE ptnowner2 PARTITION OF ptnowner FOR VALUES IN (2);
 ALTER TABLE ptnowner1 OWNER TO regress_ptnowner;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
 ERROR:  permission denied for table ptnowner
+ALTER TABLE ptnowner1 REPLICA IDENTITY USING INDEX ptnowner1_i_key;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
 ALTER TABLE ptnowner OWNER TO regress_ptnowner;
 CREATE TEMP TABLE ptnowner_oldnodes AS
@@ -560,6 +562,11 @@ CREATE TEMP TABLE ptnowner_oldnodes AS
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
 WARNING:  permission denied to execute CLUSTER on "ptnowner2", skipping it
+-- still can't repack without a replica identity
+ALTER TABLE ptnowner1 REPLICA IDENTITY DEFAULT;
+REPACK (CONCURRENTLY) ptnowner1;
+ERROR:  cannot process relation "ptnowner1"
+HINT:  Relation "ptnowner1" has no identity index.
 RESET SESSION AUTHORIZATION;
 SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
   JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
@@ -570,6 +577,15 @@ SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
  ptnowner2 | t
 (3 rows)
 
+SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
+  JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
+  relname  | ?column? 
+-----------+----------
+ ptnowner  | t
+ ptnowner1 | f
+ ptnowner2 | t
+(3 rows)
+
 DROP TABLE ptnowner;
 DROP ROLE regress_ptnowner;
 -- Test CLUSTER with external tuplesorting
diff --git a/src/test/regress/sql/cluster.sql b/src/test/regress/sql/cluster.sql
index d14063a9683..1f471a8821a 100644
--- a/src/test/regress/sql/cluster.sql
+++ b/src/test/regress/sql/cluster.sql
@@ -254,14 +254,16 @@ REPACK (CONCURRENTLY) clstrpart;
 DROP TABLE clstrpart;
 
 -- Ownership of partitions is checked
-CREATE TABLE ptnowner(i int unique) PARTITION BY LIST (i);
+CREATE TABLE ptnowner(i int unique not null) PARTITION BY LIST (i);
 CREATE INDEX ptnowner_i_idx ON ptnowner(i);
 CREATE TABLE ptnowner1 PARTITION OF ptnowner FOR VALUES IN (1);
-CREATE ROLE regress_ptnowner;
+CREATE ROLE regress_ptnowner LOGIN;
 CREATE TABLE ptnowner2 PARTITION OF ptnowner FOR VALUES IN (2);
 ALTER TABLE ptnowner1 OWNER TO regress_ptnowner;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
+ALTER TABLE ptnowner1 REPLICA IDENTITY USING INDEX ptnowner1_i_key;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
 ALTER TABLE ptnowner OWNER TO regress_ptnowner;
 CREATE TEMP TABLE ptnowner_oldnodes AS
@@ -269,7 +271,12 @@ CREATE TEMP TABLE ptnowner_oldnodes AS
   JOIN pg_class AS c ON c.oid=tree.relid;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
+-- still can't repack without a replica identity
+ALTER TABLE ptnowner1 REPLICA IDENTITY DEFAULT;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
+SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
+  JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
 SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
   JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
 DROP TABLE ptnowner;
-- 
2.47.3


--m6zy3l65ushq557m
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-REPACK-do-not-require-LOGIN-privileges.patch"



^ permalink  raw  reply  [nested|flat] 58+ messages in thread

* [PATCH 1/2] REPACK: do not require the user to have REPLICATION
@ 2026-04-20 09:38  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 58+ messages in thread

From: Álvaro Herrera @ 2026-04-20 09:38 UTC (permalink / raw)

Although REPACK (CONCURRENTLY) uses replication slots, there is no
concern that the slot will leak data of other users, because the
MAINTAIN privilege on the table is required anyway; requiring
REPLICATION is user-unfriendly without providing any actual protection.

A related aspect is that the REPLICATION attribute is not needed to
prevent REPACK from stealing slots from logical replication, since
commit e76d8c749c31 made REPACK use a separate pool of replication
slots.

Because there are now successful concurrent repack runs in the
regression tests, we're forced to run test_plan_advice under
wal_level=replica.

Author: Antonin Houska <[email protected]>
Reported-by: Justin Pryzby <[email protected]>
Reviewed-by: Chao Li <[email protected]>
Discussion: https://postgr.es/m/aeJHPNmL4vVy3oPw@pryzbyj2023
---
 src/backend/commands/repack_worker.c          |  1 -
 .../test_plan_advice/t/001_replan_regress.pl  |  1 +
 src/test/regress/expected/cluster.out         | 20 +++++++++++++++++--
 src/test/regress/sql/cluster.sql              | 11 ++++++++--
 4 files changed, 28 insertions(+), 5 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index b17edd771e2..e4a4860805b 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -214,7 +214,6 @@ repack_setup_logical_decoding(Oid relid)
 	/*
 	 * Make sure we can use logical decoding.
 	 */
-	CheckSlotPermissions();
 	CheckLogicalDecodingRequirements(true);
 
 	/*
diff --git a/src/test/modules/test_plan_advice/t/001_replan_regress.pl b/src/test/modules/test_plan_advice/t/001_replan_regress.pl
index 38ffa4d11ae..452b179a665 100644
--- a/src/test/modules/test_plan_advice/t/001_replan_regress.pl
+++ b/src/test/modules/test_plan_advice/t/001_replan_regress.pl
@@ -18,6 +18,7 @@ $node->init();
 # Set up our desired configuration.
 $node->append_conf('postgresql.conf', <<EOM);
 shared_preload_libraries='test_plan_advice'
+wal_level=replica
 pg_plan_advice.always_explain_supplied_advice=false
 pg_plan_advice.feedback_warnings=true
 EOM
diff --git a/src/test/regress/expected/cluster.out b/src/test/regress/expected/cluster.out
index 6127b215a86..e17bc91fae1 100644
--- a/src/test/regress/expected/cluster.out
+++ b/src/test/regress/expected/cluster.out
@@ -543,15 +543,17 @@ ERROR:  REPACK (CONCURRENTLY) is not supported for partitioned tables
 HINT:  Consider running the command on individual partitions.
 DROP TABLE clstrpart;
 -- Ownership of partitions is checked
-CREATE TABLE ptnowner(i int unique) PARTITION BY LIST (i);
+CREATE TABLE ptnowner(i int unique not null) PARTITION BY LIST (i);
 CREATE INDEX ptnowner_i_idx ON ptnowner(i);
 CREATE TABLE ptnowner1 PARTITION OF ptnowner FOR VALUES IN (1);
-CREATE ROLE regress_ptnowner;
+CREATE ROLE regress_ptnowner LOGIN;
 CREATE TABLE ptnowner2 PARTITION OF ptnowner FOR VALUES IN (2);
 ALTER TABLE ptnowner1 OWNER TO regress_ptnowner;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
 ERROR:  permission denied for table ptnowner
+ALTER TABLE ptnowner1 REPLICA IDENTITY USING INDEX ptnowner1_i_key;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
 ALTER TABLE ptnowner OWNER TO regress_ptnowner;
 CREATE TEMP TABLE ptnowner_oldnodes AS
@@ -560,6 +562,11 @@ CREATE TEMP TABLE ptnowner_oldnodes AS
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
 WARNING:  permission denied to execute CLUSTER on "ptnowner2", skipping it
+-- still can't repack without a replica identity
+ALTER TABLE ptnowner1 REPLICA IDENTITY DEFAULT;
+REPACK (CONCURRENTLY) ptnowner1;
+ERROR:  cannot process relation "ptnowner1"
+HINT:  Relation "ptnowner1" has no identity index.
 RESET SESSION AUTHORIZATION;
 SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
   JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
@@ -570,6 +577,15 @@ SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
  ptnowner2 | t
 (3 rows)
 
+SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
+  JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
+  relname  | ?column? 
+-----------+----------
+ ptnowner  | t
+ ptnowner1 | f
+ ptnowner2 | t
+(3 rows)
+
 DROP TABLE ptnowner;
 DROP ROLE regress_ptnowner;
 -- Test CLUSTER with external tuplesorting
diff --git a/src/test/regress/sql/cluster.sql b/src/test/regress/sql/cluster.sql
index d14063a9683..1f471a8821a 100644
--- a/src/test/regress/sql/cluster.sql
+++ b/src/test/regress/sql/cluster.sql
@@ -254,14 +254,16 @@ REPACK (CONCURRENTLY) clstrpart;
 DROP TABLE clstrpart;
 
 -- Ownership of partitions is checked
-CREATE TABLE ptnowner(i int unique) PARTITION BY LIST (i);
+CREATE TABLE ptnowner(i int unique not null) PARTITION BY LIST (i);
 CREATE INDEX ptnowner_i_idx ON ptnowner(i);
 CREATE TABLE ptnowner1 PARTITION OF ptnowner FOR VALUES IN (1);
-CREATE ROLE regress_ptnowner;
+CREATE ROLE regress_ptnowner LOGIN;
 CREATE TABLE ptnowner2 PARTITION OF ptnowner FOR VALUES IN (2);
 ALTER TABLE ptnowner1 OWNER TO regress_ptnowner;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
+ALTER TABLE ptnowner1 REPLICA IDENTITY USING INDEX ptnowner1_i_key;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
 ALTER TABLE ptnowner OWNER TO regress_ptnowner;
 CREATE TEMP TABLE ptnowner_oldnodes AS
@@ -269,7 +271,12 @@ CREATE TEMP TABLE ptnowner_oldnodes AS
   JOIN pg_class AS c ON c.oid=tree.relid;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
+-- still can't repack without a replica identity
+ALTER TABLE ptnowner1 REPLICA IDENTITY DEFAULT;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
+SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
+  JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
 SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
   JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
 DROP TABLE ptnowner;
-- 
2.47.3


--m6zy3l65ushq557m
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-REPACK-do-not-require-LOGIN-privileges.patch"



^ permalink  raw  reply  [nested|flat] 58+ messages in thread

* [PATCH 1/2] REPACK: do not require the user to have REPLICATION
@ 2026-04-20 09:38  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 58+ messages in thread

From: Álvaro Herrera @ 2026-04-20 09:38 UTC (permalink / raw)

Although REPACK (CONCURRENTLY) uses replication slots, there is no
concern that the slot will leak data of other users, because the
MAINTAIN privilege on the table is required anyway; requiring
REPLICATION is user-unfriendly without providing any actual protection.

A related aspect is that the REPLICATION attribute is not needed to
prevent REPACK from stealing slots from logical replication, since
commit e76d8c749c31 made REPACK use a separate pool of replication
slots.

Because there are now successful concurrent repack runs in the
regression tests, we're forced to run test_plan_advice under
wal_level=replica.

Author: Antonin Houska <[email protected]>
Reported-by: Justin Pryzby <[email protected]>
Reviewed-by: Chao Li <[email protected]>
Discussion: https://postgr.es/m/aeJHPNmL4vVy3oPw@pryzbyj2023
---
 src/backend/commands/repack_worker.c          |  1 -
 .../test_plan_advice/t/001_replan_regress.pl  |  1 +
 src/test/regress/expected/cluster.out         | 20 +++++++++++++++++--
 src/test/regress/sql/cluster.sql              | 11 ++++++++--
 4 files changed, 28 insertions(+), 5 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index b17edd771e2..e4a4860805b 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -214,7 +214,6 @@ repack_setup_logical_decoding(Oid relid)
 	/*
 	 * Make sure we can use logical decoding.
 	 */
-	CheckSlotPermissions();
 	CheckLogicalDecodingRequirements(true);
 
 	/*
diff --git a/src/test/modules/test_plan_advice/t/001_replan_regress.pl b/src/test/modules/test_plan_advice/t/001_replan_regress.pl
index 38ffa4d11ae..452b179a665 100644
--- a/src/test/modules/test_plan_advice/t/001_replan_regress.pl
+++ b/src/test/modules/test_plan_advice/t/001_replan_regress.pl
@@ -18,6 +18,7 @@ $node->init();
 # Set up our desired configuration.
 $node->append_conf('postgresql.conf', <<EOM);
 shared_preload_libraries='test_plan_advice'
+wal_level=replica
 pg_plan_advice.always_explain_supplied_advice=false
 pg_plan_advice.feedback_warnings=true
 EOM
diff --git a/src/test/regress/expected/cluster.out b/src/test/regress/expected/cluster.out
index 6127b215a86..e17bc91fae1 100644
--- a/src/test/regress/expected/cluster.out
+++ b/src/test/regress/expected/cluster.out
@@ -543,15 +543,17 @@ ERROR:  REPACK (CONCURRENTLY) is not supported for partitioned tables
 HINT:  Consider running the command on individual partitions.
 DROP TABLE clstrpart;
 -- Ownership of partitions is checked
-CREATE TABLE ptnowner(i int unique) PARTITION BY LIST (i);
+CREATE TABLE ptnowner(i int unique not null) PARTITION BY LIST (i);
 CREATE INDEX ptnowner_i_idx ON ptnowner(i);
 CREATE TABLE ptnowner1 PARTITION OF ptnowner FOR VALUES IN (1);
-CREATE ROLE regress_ptnowner;
+CREATE ROLE regress_ptnowner LOGIN;
 CREATE TABLE ptnowner2 PARTITION OF ptnowner FOR VALUES IN (2);
 ALTER TABLE ptnowner1 OWNER TO regress_ptnowner;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
 ERROR:  permission denied for table ptnowner
+ALTER TABLE ptnowner1 REPLICA IDENTITY USING INDEX ptnowner1_i_key;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
 ALTER TABLE ptnowner OWNER TO regress_ptnowner;
 CREATE TEMP TABLE ptnowner_oldnodes AS
@@ -560,6 +562,11 @@ CREATE TEMP TABLE ptnowner_oldnodes AS
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
 WARNING:  permission denied to execute CLUSTER on "ptnowner2", skipping it
+-- still can't repack without a replica identity
+ALTER TABLE ptnowner1 REPLICA IDENTITY DEFAULT;
+REPACK (CONCURRENTLY) ptnowner1;
+ERROR:  cannot process relation "ptnowner1"
+HINT:  Relation "ptnowner1" has no identity index.
 RESET SESSION AUTHORIZATION;
 SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
   JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
@@ -570,6 +577,15 @@ SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
  ptnowner2 | t
 (3 rows)
 
+SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
+  JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
+  relname  | ?column? 
+-----------+----------
+ ptnowner  | t
+ ptnowner1 | f
+ ptnowner2 | t
+(3 rows)
+
 DROP TABLE ptnowner;
 DROP ROLE regress_ptnowner;
 -- Test CLUSTER with external tuplesorting
diff --git a/src/test/regress/sql/cluster.sql b/src/test/regress/sql/cluster.sql
index d14063a9683..1f471a8821a 100644
--- a/src/test/regress/sql/cluster.sql
+++ b/src/test/regress/sql/cluster.sql
@@ -254,14 +254,16 @@ REPACK (CONCURRENTLY) clstrpart;
 DROP TABLE clstrpart;
 
 -- Ownership of partitions is checked
-CREATE TABLE ptnowner(i int unique) PARTITION BY LIST (i);
+CREATE TABLE ptnowner(i int unique not null) PARTITION BY LIST (i);
 CREATE INDEX ptnowner_i_idx ON ptnowner(i);
 CREATE TABLE ptnowner1 PARTITION OF ptnowner FOR VALUES IN (1);
-CREATE ROLE regress_ptnowner;
+CREATE ROLE regress_ptnowner LOGIN;
 CREATE TABLE ptnowner2 PARTITION OF ptnowner FOR VALUES IN (2);
 ALTER TABLE ptnowner1 OWNER TO regress_ptnowner;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
+ALTER TABLE ptnowner1 REPLICA IDENTITY USING INDEX ptnowner1_i_key;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
 ALTER TABLE ptnowner OWNER TO regress_ptnowner;
 CREATE TEMP TABLE ptnowner_oldnodes AS
@@ -269,7 +271,12 @@ CREATE TEMP TABLE ptnowner_oldnodes AS
   JOIN pg_class AS c ON c.oid=tree.relid;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
+-- still can't repack without a replica identity
+ALTER TABLE ptnowner1 REPLICA IDENTITY DEFAULT;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
+SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
+  JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
 SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
   JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
 DROP TABLE ptnowner;
-- 
2.47.3


--m6zy3l65ushq557m
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-REPACK-do-not-require-LOGIN-privileges.patch"



^ permalink  raw  reply  [nested|flat] 58+ messages in thread

* [PATCH 1/2] REPACK: do not require the user to have REPLICATION
@ 2026-04-20 09:38  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 58+ messages in thread

From: Álvaro Herrera @ 2026-04-20 09:38 UTC (permalink / raw)

Although REPACK (CONCURRENTLY) uses replication slots, there is no
concern that the slot will leak data of other users, because the
MAINTAIN privilege on the table is required anyway; requiring
REPLICATION is user-unfriendly without providing any actual protection.

A related aspect is that the REPLICATION attribute is not needed to
prevent REPACK from stealing slots from logical replication, since
commit e76d8c749c31 made REPACK use a separate pool of replication
slots.

Because there are now successful concurrent repack runs in the
regression tests, we're forced to run test_plan_advice under
wal_level=replica.

Author: Antonin Houska <[email protected]>
Reported-by: Justin Pryzby <[email protected]>
Reviewed-by: Chao Li <[email protected]>
Discussion: https://postgr.es/m/aeJHPNmL4vVy3oPw@pryzbyj2023
---
 src/backend/commands/repack_worker.c          |  1 -
 .../test_plan_advice/t/001_replan_regress.pl  |  1 +
 src/test/regress/expected/cluster.out         | 20 +++++++++++++++++--
 src/test/regress/sql/cluster.sql              | 11 ++++++++--
 4 files changed, 28 insertions(+), 5 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index b17edd771e2..e4a4860805b 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -214,7 +214,6 @@ repack_setup_logical_decoding(Oid relid)
 	/*
 	 * Make sure we can use logical decoding.
 	 */
-	CheckSlotPermissions();
 	CheckLogicalDecodingRequirements(true);
 
 	/*
diff --git a/src/test/modules/test_plan_advice/t/001_replan_regress.pl b/src/test/modules/test_plan_advice/t/001_replan_regress.pl
index 38ffa4d11ae..452b179a665 100644
--- a/src/test/modules/test_plan_advice/t/001_replan_regress.pl
+++ b/src/test/modules/test_plan_advice/t/001_replan_regress.pl
@@ -18,6 +18,7 @@ $node->init();
 # Set up our desired configuration.
 $node->append_conf('postgresql.conf', <<EOM);
 shared_preload_libraries='test_plan_advice'
+wal_level=replica
 pg_plan_advice.always_explain_supplied_advice=false
 pg_plan_advice.feedback_warnings=true
 EOM
diff --git a/src/test/regress/expected/cluster.out b/src/test/regress/expected/cluster.out
index 6127b215a86..e17bc91fae1 100644
--- a/src/test/regress/expected/cluster.out
+++ b/src/test/regress/expected/cluster.out
@@ -543,15 +543,17 @@ ERROR:  REPACK (CONCURRENTLY) is not supported for partitioned tables
 HINT:  Consider running the command on individual partitions.
 DROP TABLE clstrpart;
 -- Ownership of partitions is checked
-CREATE TABLE ptnowner(i int unique) PARTITION BY LIST (i);
+CREATE TABLE ptnowner(i int unique not null) PARTITION BY LIST (i);
 CREATE INDEX ptnowner_i_idx ON ptnowner(i);
 CREATE TABLE ptnowner1 PARTITION OF ptnowner FOR VALUES IN (1);
-CREATE ROLE regress_ptnowner;
+CREATE ROLE regress_ptnowner LOGIN;
 CREATE TABLE ptnowner2 PARTITION OF ptnowner FOR VALUES IN (2);
 ALTER TABLE ptnowner1 OWNER TO regress_ptnowner;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
 ERROR:  permission denied for table ptnowner
+ALTER TABLE ptnowner1 REPLICA IDENTITY USING INDEX ptnowner1_i_key;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
 ALTER TABLE ptnowner OWNER TO regress_ptnowner;
 CREATE TEMP TABLE ptnowner_oldnodes AS
@@ -560,6 +562,11 @@ CREATE TEMP TABLE ptnowner_oldnodes AS
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
 WARNING:  permission denied to execute CLUSTER on "ptnowner2", skipping it
+-- still can't repack without a replica identity
+ALTER TABLE ptnowner1 REPLICA IDENTITY DEFAULT;
+REPACK (CONCURRENTLY) ptnowner1;
+ERROR:  cannot process relation "ptnowner1"
+HINT:  Relation "ptnowner1" has no identity index.
 RESET SESSION AUTHORIZATION;
 SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
   JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
@@ -570,6 +577,15 @@ SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
  ptnowner2 | t
 (3 rows)
 
+SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
+  JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
+  relname  | ?column? 
+-----------+----------
+ ptnowner  | t
+ ptnowner1 | f
+ ptnowner2 | t
+(3 rows)
+
 DROP TABLE ptnowner;
 DROP ROLE regress_ptnowner;
 -- Test CLUSTER with external tuplesorting
diff --git a/src/test/regress/sql/cluster.sql b/src/test/regress/sql/cluster.sql
index d14063a9683..1f471a8821a 100644
--- a/src/test/regress/sql/cluster.sql
+++ b/src/test/regress/sql/cluster.sql
@@ -254,14 +254,16 @@ REPACK (CONCURRENTLY) clstrpart;
 DROP TABLE clstrpart;
 
 -- Ownership of partitions is checked
-CREATE TABLE ptnowner(i int unique) PARTITION BY LIST (i);
+CREATE TABLE ptnowner(i int unique not null) PARTITION BY LIST (i);
 CREATE INDEX ptnowner_i_idx ON ptnowner(i);
 CREATE TABLE ptnowner1 PARTITION OF ptnowner FOR VALUES IN (1);
-CREATE ROLE regress_ptnowner;
+CREATE ROLE regress_ptnowner LOGIN;
 CREATE TABLE ptnowner2 PARTITION OF ptnowner FOR VALUES IN (2);
 ALTER TABLE ptnowner1 OWNER TO regress_ptnowner;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
+ALTER TABLE ptnowner1 REPLICA IDENTITY USING INDEX ptnowner1_i_key;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
 ALTER TABLE ptnowner OWNER TO regress_ptnowner;
 CREATE TEMP TABLE ptnowner_oldnodes AS
@@ -269,7 +271,12 @@ CREATE TEMP TABLE ptnowner_oldnodes AS
   JOIN pg_class AS c ON c.oid=tree.relid;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
+-- still can't repack without a replica identity
+ALTER TABLE ptnowner1 REPLICA IDENTITY DEFAULT;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
+SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
+  JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
 SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
   JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
 DROP TABLE ptnowner;
-- 
2.47.3


--m6zy3l65ushq557m
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-REPACK-do-not-require-LOGIN-privileges.patch"



^ permalink  raw  reply  [nested|flat] 58+ messages in thread

* [PATCH 1/2] REPACK: do not require the user to have REPLICATION
@ 2026-04-20 09:38  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 58+ messages in thread

From: Álvaro Herrera @ 2026-04-20 09:38 UTC (permalink / raw)

Although REPACK (CONCURRENTLY) uses replication slots, there is no
concern that the slot will leak data of other users, because the
MAINTAIN privilege on the table is required anyway; requiring
REPLICATION is user-unfriendly without providing any actual protection.

A related aspect is that the REPLICATION attribute is not needed to
prevent REPACK from stealing slots from logical replication, since
commit e76d8c749c31 made REPACK use a separate pool of replication
slots.

Because there are now successful concurrent repack runs in the
regression tests, we're forced to run test_plan_advice under
wal_level=replica.

Author: Antonin Houska <[email protected]>
Reported-by: Justin Pryzby <[email protected]>
Reviewed-by: Chao Li <[email protected]>
Discussion: https://postgr.es/m/aeJHPNmL4vVy3oPw@pryzbyj2023
---
 src/backend/commands/repack_worker.c          |  1 -
 .../test_plan_advice/t/001_replan_regress.pl  |  1 +
 src/test/regress/expected/cluster.out         | 20 +++++++++++++++++--
 src/test/regress/sql/cluster.sql              | 11 ++++++++--
 4 files changed, 28 insertions(+), 5 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index b17edd771e2..e4a4860805b 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -214,7 +214,6 @@ repack_setup_logical_decoding(Oid relid)
 	/*
 	 * Make sure we can use logical decoding.
 	 */
-	CheckSlotPermissions();
 	CheckLogicalDecodingRequirements(true);
 
 	/*
diff --git a/src/test/modules/test_plan_advice/t/001_replan_regress.pl b/src/test/modules/test_plan_advice/t/001_replan_regress.pl
index 38ffa4d11ae..452b179a665 100644
--- a/src/test/modules/test_plan_advice/t/001_replan_regress.pl
+++ b/src/test/modules/test_plan_advice/t/001_replan_regress.pl
@@ -18,6 +18,7 @@ $node->init();
 # Set up our desired configuration.
 $node->append_conf('postgresql.conf', <<EOM);
 shared_preload_libraries='test_plan_advice'
+wal_level=replica
 pg_plan_advice.always_explain_supplied_advice=false
 pg_plan_advice.feedback_warnings=true
 EOM
diff --git a/src/test/regress/expected/cluster.out b/src/test/regress/expected/cluster.out
index 6127b215a86..e17bc91fae1 100644
--- a/src/test/regress/expected/cluster.out
+++ b/src/test/regress/expected/cluster.out
@@ -543,15 +543,17 @@ ERROR:  REPACK (CONCURRENTLY) is not supported for partitioned tables
 HINT:  Consider running the command on individual partitions.
 DROP TABLE clstrpart;
 -- Ownership of partitions is checked
-CREATE TABLE ptnowner(i int unique) PARTITION BY LIST (i);
+CREATE TABLE ptnowner(i int unique not null) PARTITION BY LIST (i);
 CREATE INDEX ptnowner_i_idx ON ptnowner(i);
 CREATE TABLE ptnowner1 PARTITION OF ptnowner FOR VALUES IN (1);
-CREATE ROLE regress_ptnowner;
+CREATE ROLE regress_ptnowner LOGIN;
 CREATE TABLE ptnowner2 PARTITION OF ptnowner FOR VALUES IN (2);
 ALTER TABLE ptnowner1 OWNER TO regress_ptnowner;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
 ERROR:  permission denied for table ptnowner
+ALTER TABLE ptnowner1 REPLICA IDENTITY USING INDEX ptnowner1_i_key;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
 ALTER TABLE ptnowner OWNER TO regress_ptnowner;
 CREATE TEMP TABLE ptnowner_oldnodes AS
@@ -560,6 +562,11 @@ CREATE TEMP TABLE ptnowner_oldnodes AS
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
 WARNING:  permission denied to execute CLUSTER on "ptnowner2", skipping it
+-- still can't repack without a replica identity
+ALTER TABLE ptnowner1 REPLICA IDENTITY DEFAULT;
+REPACK (CONCURRENTLY) ptnowner1;
+ERROR:  cannot process relation "ptnowner1"
+HINT:  Relation "ptnowner1" has no identity index.
 RESET SESSION AUTHORIZATION;
 SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
   JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
@@ -570,6 +577,15 @@ SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
  ptnowner2 | t
 (3 rows)
 
+SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
+  JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
+  relname  | ?column? 
+-----------+----------
+ ptnowner  | t
+ ptnowner1 | f
+ ptnowner2 | t
+(3 rows)
+
 DROP TABLE ptnowner;
 DROP ROLE regress_ptnowner;
 -- Test CLUSTER with external tuplesorting
diff --git a/src/test/regress/sql/cluster.sql b/src/test/regress/sql/cluster.sql
index d14063a9683..1f471a8821a 100644
--- a/src/test/regress/sql/cluster.sql
+++ b/src/test/regress/sql/cluster.sql
@@ -254,14 +254,16 @@ REPACK (CONCURRENTLY) clstrpart;
 DROP TABLE clstrpart;
 
 -- Ownership of partitions is checked
-CREATE TABLE ptnowner(i int unique) PARTITION BY LIST (i);
+CREATE TABLE ptnowner(i int unique not null) PARTITION BY LIST (i);
 CREATE INDEX ptnowner_i_idx ON ptnowner(i);
 CREATE TABLE ptnowner1 PARTITION OF ptnowner FOR VALUES IN (1);
-CREATE ROLE regress_ptnowner;
+CREATE ROLE regress_ptnowner LOGIN;
 CREATE TABLE ptnowner2 PARTITION OF ptnowner FOR VALUES IN (2);
 ALTER TABLE ptnowner1 OWNER TO regress_ptnowner;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
+ALTER TABLE ptnowner1 REPLICA IDENTITY USING INDEX ptnowner1_i_key;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
 ALTER TABLE ptnowner OWNER TO regress_ptnowner;
 CREATE TEMP TABLE ptnowner_oldnodes AS
@@ -269,7 +271,12 @@ CREATE TEMP TABLE ptnowner_oldnodes AS
   JOIN pg_class AS c ON c.oid=tree.relid;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
+-- still can't repack without a replica identity
+ALTER TABLE ptnowner1 REPLICA IDENTITY DEFAULT;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
+SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
+  JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
 SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
   JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
 DROP TABLE ptnowner;
-- 
2.47.3


--m6zy3l65ushq557m
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-REPACK-do-not-require-LOGIN-privileges.patch"



^ permalink  raw  reply  [nested|flat] 58+ messages in thread

* [PATCH 1/2] REPACK: do not require the user to have REPLICATION
@ 2026-04-20 09:38  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 58+ messages in thread

From: Álvaro Herrera @ 2026-04-20 09:38 UTC (permalink / raw)

Although REPACK (CONCURRENTLY) uses replication slots, there is no
concern that the slot will leak data of other users, because the
MAINTAIN privilege on the table is required anyway; requiring
REPLICATION is user-unfriendly without providing any actual protection.

A related aspect is that the REPLICATION attribute is not needed to
prevent REPACK from stealing slots from logical replication, since
commit e76d8c749c31 made REPACK use a separate pool of replication
slots.

Because there are now successful concurrent repack runs in the
regression tests, we're forced to run test_plan_advice under
wal_level=replica.

Author: Antonin Houska <[email protected]>
Reported-by: Justin Pryzby <[email protected]>
Reviewed-by: Chao Li <[email protected]>
Discussion: https://postgr.es/m/aeJHPNmL4vVy3oPw@pryzbyj2023
---
 src/backend/commands/repack_worker.c          |  1 -
 .../test_plan_advice/t/001_replan_regress.pl  |  1 +
 src/test/regress/expected/cluster.out         | 20 +++++++++++++++++--
 src/test/regress/sql/cluster.sql              | 11 ++++++++--
 4 files changed, 28 insertions(+), 5 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index b17edd771e2..e4a4860805b 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -214,7 +214,6 @@ repack_setup_logical_decoding(Oid relid)
 	/*
 	 * Make sure we can use logical decoding.
 	 */
-	CheckSlotPermissions();
 	CheckLogicalDecodingRequirements(true);
 
 	/*
diff --git a/src/test/modules/test_plan_advice/t/001_replan_regress.pl b/src/test/modules/test_plan_advice/t/001_replan_regress.pl
index 38ffa4d11ae..452b179a665 100644
--- a/src/test/modules/test_plan_advice/t/001_replan_regress.pl
+++ b/src/test/modules/test_plan_advice/t/001_replan_regress.pl
@@ -18,6 +18,7 @@ $node->init();
 # Set up our desired configuration.
 $node->append_conf('postgresql.conf', <<EOM);
 shared_preload_libraries='test_plan_advice'
+wal_level=replica
 pg_plan_advice.always_explain_supplied_advice=false
 pg_plan_advice.feedback_warnings=true
 EOM
diff --git a/src/test/regress/expected/cluster.out b/src/test/regress/expected/cluster.out
index 6127b215a86..e17bc91fae1 100644
--- a/src/test/regress/expected/cluster.out
+++ b/src/test/regress/expected/cluster.out
@@ -543,15 +543,17 @@ ERROR:  REPACK (CONCURRENTLY) is not supported for partitioned tables
 HINT:  Consider running the command on individual partitions.
 DROP TABLE clstrpart;
 -- Ownership of partitions is checked
-CREATE TABLE ptnowner(i int unique) PARTITION BY LIST (i);
+CREATE TABLE ptnowner(i int unique not null) PARTITION BY LIST (i);
 CREATE INDEX ptnowner_i_idx ON ptnowner(i);
 CREATE TABLE ptnowner1 PARTITION OF ptnowner FOR VALUES IN (1);
-CREATE ROLE regress_ptnowner;
+CREATE ROLE regress_ptnowner LOGIN;
 CREATE TABLE ptnowner2 PARTITION OF ptnowner FOR VALUES IN (2);
 ALTER TABLE ptnowner1 OWNER TO regress_ptnowner;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
 ERROR:  permission denied for table ptnowner
+ALTER TABLE ptnowner1 REPLICA IDENTITY USING INDEX ptnowner1_i_key;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
 ALTER TABLE ptnowner OWNER TO regress_ptnowner;
 CREATE TEMP TABLE ptnowner_oldnodes AS
@@ -560,6 +562,11 @@ CREATE TEMP TABLE ptnowner_oldnodes AS
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
 WARNING:  permission denied to execute CLUSTER on "ptnowner2", skipping it
+-- still can't repack without a replica identity
+ALTER TABLE ptnowner1 REPLICA IDENTITY DEFAULT;
+REPACK (CONCURRENTLY) ptnowner1;
+ERROR:  cannot process relation "ptnowner1"
+HINT:  Relation "ptnowner1" has no identity index.
 RESET SESSION AUTHORIZATION;
 SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
   JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
@@ -570,6 +577,15 @@ SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
  ptnowner2 | t
 (3 rows)
 
+SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
+  JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
+  relname  | ?column? 
+-----------+----------
+ ptnowner  | t
+ ptnowner1 | f
+ ptnowner2 | t
+(3 rows)
+
 DROP TABLE ptnowner;
 DROP ROLE regress_ptnowner;
 -- Test CLUSTER with external tuplesorting
diff --git a/src/test/regress/sql/cluster.sql b/src/test/regress/sql/cluster.sql
index d14063a9683..1f471a8821a 100644
--- a/src/test/regress/sql/cluster.sql
+++ b/src/test/regress/sql/cluster.sql
@@ -254,14 +254,16 @@ REPACK (CONCURRENTLY) clstrpart;
 DROP TABLE clstrpart;
 
 -- Ownership of partitions is checked
-CREATE TABLE ptnowner(i int unique) PARTITION BY LIST (i);
+CREATE TABLE ptnowner(i int unique not null) PARTITION BY LIST (i);
 CREATE INDEX ptnowner_i_idx ON ptnowner(i);
 CREATE TABLE ptnowner1 PARTITION OF ptnowner FOR VALUES IN (1);
-CREATE ROLE regress_ptnowner;
+CREATE ROLE regress_ptnowner LOGIN;
 CREATE TABLE ptnowner2 PARTITION OF ptnowner FOR VALUES IN (2);
 ALTER TABLE ptnowner1 OWNER TO regress_ptnowner;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
+ALTER TABLE ptnowner1 REPLICA IDENTITY USING INDEX ptnowner1_i_key;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
 ALTER TABLE ptnowner OWNER TO regress_ptnowner;
 CREATE TEMP TABLE ptnowner_oldnodes AS
@@ -269,7 +271,12 @@ CREATE TEMP TABLE ptnowner_oldnodes AS
   JOIN pg_class AS c ON c.oid=tree.relid;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
+-- still can't repack without a replica identity
+ALTER TABLE ptnowner1 REPLICA IDENTITY DEFAULT;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
+SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
+  JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
 SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
   JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
 DROP TABLE ptnowner;
-- 
2.47.3


--m6zy3l65ushq557m
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-REPACK-do-not-require-LOGIN-privileges.patch"



^ permalink  raw  reply  [nested|flat] 58+ messages in thread

* [PATCH 1/2] REPACK: do not require the user to have REPLICATION
@ 2026-04-20 09:38  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 58+ messages in thread

From: Álvaro Herrera @ 2026-04-20 09:38 UTC (permalink / raw)

Although REPACK (CONCURRENTLY) uses replication slots, there is no
concern that the slot will leak data of other users, because the
MAINTAIN privilege on the table is required anyway; requiring
REPLICATION is user-unfriendly without providing any actual protection.

A related aspect is that the REPLICATION attribute is not needed to
prevent REPACK from stealing slots from logical replication, since
commit e76d8c749c31 made REPACK use a separate pool of replication
slots.

Because there are now successful concurrent repack runs in the
regression tests, we're forced to run test_plan_advice under
wal_level=replica.

Author: Antonin Houska <[email protected]>
Reported-by: Justin Pryzby <[email protected]>
Reviewed-by: Chao Li <[email protected]>
Discussion: https://postgr.es/m/aeJHPNmL4vVy3oPw@pryzbyj2023
---
 src/backend/commands/repack_worker.c          |  1 -
 .../test_plan_advice/t/001_replan_regress.pl  |  1 +
 src/test/regress/expected/cluster.out         | 20 +++++++++++++++++--
 src/test/regress/sql/cluster.sql              | 11 ++++++++--
 4 files changed, 28 insertions(+), 5 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index b17edd771e2..e4a4860805b 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -214,7 +214,6 @@ repack_setup_logical_decoding(Oid relid)
 	/*
 	 * Make sure we can use logical decoding.
 	 */
-	CheckSlotPermissions();
 	CheckLogicalDecodingRequirements(true);
 
 	/*
diff --git a/src/test/modules/test_plan_advice/t/001_replan_regress.pl b/src/test/modules/test_plan_advice/t/001_replan_regress.pl
index 38ffa4d11ae..452b179a665 100644
--- a/src/test/modules/test_plan_advice/t/001_replan_regress.pl
+++ b/src/test/modules/test_plan_advice/t/001_replan_regress.pl
@@ -18,6 +18,7 @@ $node->init();
 # Set up our desired configuration.
 $node->append_conf('postgresql.conf', <<EOM);
 shared_preload_libraries='test_plan_advice'
+wal_level=replica
 pg_plan_advice.always_explain_supplied_advice=false
 pg_plan_advice.feedback_warnings=true
 EOM
diff --git a/src/test/regress/expected/cluster.out b/src/test/regress/expected/cluster.out
index 6127b215a86..e17bc91fae1 100644
--- a/src/test/regress/expected/cluster.out
+++ b/src/test/regress/expected/cluster.out
@@ -543,15 +543,17 @@ ERROR:  REPACK (CONCURRENTLY) is not supported for partitioned tables
 HINT:  Consider running the command on individual partitions.
 DROP TABLE clstrpart;
 -- Ownership of partitions is checked
-CREATE TABLE ptnowner(i int unique) PARTITION BY LIST (i);
+CREATE TABLE ptnowner(i int unique not null) PARTITION BY LIST (i);
 CREATE INDEX ptnowner_i_idx ON ptnowner(i);
 CREATE TABLE ptnowner1 PARTITION OF ptnowner FOR VALUES IN (1);
-CREATE ROLE regress_ptnowner;
+CREATE ROLE regress_ptnowner LOGIN;
 CREATE TABLE ptnowner2 PARTITION OF ptnowner FOR VALUES IN (2);
 ALTER TABLE ptnowner1 OWNER TO regress_ptnowner;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
 ERROR:  permission denied for table ptnowner
+ALTER TABLE ptnowner1 REPLICA IDENTITY USING INDEX ptnowner1_i_key;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
 ALTER TABLE ptnowner OWNER TO regress_ptnowner;
 CREATE TEMP TABLE ptnowner_oldnodes AS
@@ -560,6 +562,11 @@ CREATE TEMP TABLE ptnowner_oldnodes AS
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
 WARNING:  permission denied to execute CLUSTER on "ptnowner2", skipping it
+-- still can't repack without a replica identity
+ALTER TABLE ptnowner1 REPLICA IDENTITY DEFAULT;
+REPACK (CONCURRENTLY) ptnowner1;
+ERROR:  cannot process relation "ptnowner1"
+HINT:  Relation "ptnowner1" has no identity index.
 RESET SESSION AUTHORIZATION;
 SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
   JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
@@ -570,6 +577,15 @@ SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
  ptnowner2 | t
 (3 rows)
 
+SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
+  JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
+  relname  | ?column? 
+-----------+----------
+ ptnowner  | t
+ ptnowner1 | f
+ ptnowner2 | t
+(3 rows)
+
 DROP TABLE ptnowner;
 DROP ROLE regress_ptnowner;
 -- Test CLUSTER with external tuplesorting
diff --git a/src/test/regress/sql/cluster.sql b/src/test/regress/sql/cluster.sql
index d14063a9683..1f471a8821a 100644
--- a/src/test/regress/sql/cluster.sql
+++ b/src/test/regress/sql/cluster.sql
@@ -254,14 +254,16 @@ REPACK (CONCURRENTLY) clstrpart;
 DROP TABLE clstrpart;
 
 -- Ownership of partitions is checked
-CREATE TABLE ptnowner(i int unique) PARTITION BY LIST (i);
+CREATE TABLE ptnowner(i int unique not null) PARTITION BY LIST (i);
 CREATE INDEX ptnowner_i_idx ON ptnowner(i);
 CREATE TABLE ptnowner1 PARTITION OF ptnowner FOR VALUES IN (1);
-CREATE ROLE regress_ptnowner;
+CREATE ROLE regress_ptnowner LOGIN;
 CREATE TABLE ptnowner2 PARTITION OF ptnowner FOR VALUES IN (2);
 ALTER TABLE ptnowner1 OWNER TO regress_ptnowner;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
+ALTER TABLE ptnowner1 REPLICA IDENTITY USING INDEX ptnowner1_i_key;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
 ALTER TABLE ptnowner OWNER TO regress_ptnowner;
 CREATE TEMP TABLE ptnowner_oldnodes AS
@@ -269,7 +271,12 @@ CREATE TEMP TABLE ptnowner_oldnodes AS
   JOIN pg_class AS c ON c.oid=tree.relid;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
+-- still can't repack without a replica identity
+ALTER TABLE ptnowner1 REPLICA IDENTITY DEFAULT;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
+SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
+  JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
 SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
   JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
 DROP TABLE ptnowner;
-- 
2.47.3


--m6zy3l65ushq557m
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-REPACK-do-not-require-LOGIN-privileges.patch"



^ permalink  raw  reply  [nested|flat] 58+ messages in thread

* [PATCH 1/2] REPACK: do not require the user to have REPLICATION
@ 2026-04-20 09:38  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 58+ messages in thread

From: Álvaro Herrera @ 2026-04-20 09:38 UTC (permalink / raw)

Although REPACK (CONCURRENTLY) uses replication slots, there is no
concern that the slot will leak data of other users, because the
MAINTAIN privilege on the table is required anyway; requiring
REPLICATION is user-unfriendly without providing any actual protection.

A related aspect is that the REPLICATION attribute is not needed to
prevent REPACK from stealing slots from logical replication, since
commit e76d8c749c31 made REPACK use a separate pool of replication
slots.

Because there are now successful concurrent repack runs in the
regression tests, we're forced to run test_plan_advice under
wal_level=replica.

Author: Antonin Houska <[email protected]>
Reported-by: Justin Pryzby <[email protected]>
Reviewed-by: Chao Li <[email protected]>
Discussion: https://postgr.es/m/aeJHPNmL4vVy3oPw@pryzbyj2023
---
 src/backend/commands/repack_worker.c          |  1 -
 .../test_plan_advice/t/001_replan_regress.pl  |  1 +
 src/test/regress/expected/cluster.out         | 20 +++++++++++++++++--
 src/test/regress/sql/cluster.sql              | 11 ++++++++--
 4 files changed, 28 insertions(+), 5 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index b17edd771e2..e4a4860805b 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -214,7 +214,6 @@ repack_setup_logical_decoding(Oid relid)
 	/*
 	 * Make sure we can use logical decoding.
 	 */
-	CheckSlotPermissions();
 	CheckLogicalDecodingRequirements(true);
 
 	/*
diff --git a/src/test/modules/test_plan_advice/t/001_replan_regress.pl b/src/test/modules/test_plan_advice/t/001_replan_regress.pl
index 38ffa4d11ae..452b179a665 100644
--- a/src/test/modules/test_plan_advice/t/001_replan_regress.pl
+++ b/src/test/modules/test_plan_advice/t/001_replan_regress.pl
@@ -18,6 +18,7 @@ $node->init();
 # Set up our desired configuration.
 $node->append_conf('postgresql.conf', <<EOM);
 shared_preload_libraries='test_plan_advice'
+wal_level=replica
 pg_plan_advice.always_explain_supplied_advice=false
 pg_plan_advice.feedback_warnings=true
 EOM
diff --git a/src/test/regress/expected/cluster.out b/src/test/regress/expected/cluster.out
index 6127b215a86..e17bc91fae1 100644
--- a/src/test/regress/expected/cluster.out
+++ b/src/test/regress/expected/cluster.out
@@ -543,15 +543,17 @@ ERROR:  REPACK (CONCURRENTLY) is not supported for partitioned tables
 HINT:  Consider running the command on individual partitions.
 DROP TABLE clstrpart;
 -- Ownership of partitions is checked
-CREATE TABLE ptnowner(i int unique) PARTITION BY LIST (i);
+CREATE TABLE ptnowner(i int unique not null) PARTITION BY LIST (i);
 CREATE INDEX ptnowner_i_idx ON ptnowner(i);
 CREATE TABLE ptnowner1 PARTITION OF ptnowner FOR VALUES IN (1);
-CREATE ROLE regress_ptnowner;
+CREATE ROLE regress_ptnowner LOGIN;
 CREATE TABLE ptnowner2 PARTITION OF ptnowner FOR VALUES IN (2);
 ALTER TABLE ptnowner1 OWNER TO regress_ptnowner;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
 ERROR:  permission denied for table ptnowner
+ALTER TABLE ptnowner1 REPLICA IDENTITY USING INDEX ptnowner1_i_key;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
 ALTER TABLE ptnowner OWNER TO regress_ptnowner;
 CREATE TEMP TABLE ptnowner_oldnodes AS
@@ -560,6 +562,11 @@ CREATE TEMP TABLE ptnowner_oldnodes AS
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
 WARNING:  permission denied to execute CLUSTER on "ptnowner2", skipping it
+-- still can't repack without a replica identity
+ALTER TABLE ptnowner1 REPLICA IDENTITY DEFAULT;
+REPACK (CONCURRENTLY) ptnowner1;
+ERROR:  cannot process relation "ptnowner1"
+HINT:  Relation "ptnowner1" has no identity index.
 RESET SESSION AUTHORIZATION;
 SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
   JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
@@ -570,6 +577,15 @@ SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
  ptnowner2 | t
 (3 rows)
 
+SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
+  JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
+  relname  | ?column? 
+-----------+----------
+ ptnowner  | t
+ ptnowner1 | f
+ ptnowner2 | t
+(3 rows)
+
 DROP TABLE ptnowner;
 DROP ROLE regress_ptnowner;
 -- Test CLUSTER with external tuplesorting
diff --git a/src/test/regress/sql/cluster.sql b/src/test/regress/sql/cluster.sql
index d14063a9683..1f471a8821a 100644
--- a/src/test/regress/sql/cluster.sql
+++ b/src/test/regress/sql/cluster.sql
@@ -254,14 +254,16 @@ REPACK (CONCURRENTLY) clstrpart;
 DROP TABLE clstrpart;
 
 -- Ownership of partitions is checked
-CREATE TABLE ptnowner(i int unique) PARTITION BY LIST (i);
+CREATE TABLE ptnowner(i int unique not null) PARTITION BY LIST (i);
 CREATE INDEX ptnowner_i_idx ON ptnowner(i);
 CREATE TABLE ptnowner1 PARTITION OF ptnowner FOR VALUES IN (1);
-CREATE ROLE regress_ptnowner;
+CREATE ROLE regress_ptnowner LOGIN;
 CREATE TABLE ptnowner2 PARTITION OF ptnowner FOR VALUES IN (2);
 ALTER TABLE ptnowner1 OWNER TO regress_ptnowner;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
+ALTER TABLE ptnowner1 REPLICA IDENTITY USING INDEX ptnowner1_i_key;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
 ALTER TABLE ptnowner OWNER TO regress_ptnowner;
 CREATE TEMP TABLE ptnowner_oldnodes AS
@@ -269,7 +271,12 @@ CREATE TEMP TABLE ptnowner_oldnodes AS
   JOIN pg_class AS c ON c.oid=tree.relid;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
+-- still can't repack without a replica identity
+ALTER TABLE ptnowner1 REPLICA IDENTITY DEFAULT;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
+SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
+  JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
 SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
   JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
 DROP TABLE ptnowner;
-- 
2.47.3


--m6zy3l65ushq557m
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-REPACK-do-not-require-LOGIN-privileges.patch"



^ permalink  raw  reply  [nested|flat] 58+ messages in thread

* [PATCH 1/2] REPACK: do not require the user to have REPLICATION
@ 2026-04-20 09:38  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 58+ messages in thread

From: Álvaro Herrera @ 2026-04-20 09:38 UTC (permalink / raw)

Although REPACK (CONCURRENTLY) uses replication slots, there is no
concern that the slot will leak data of other users, because the
MAINTAIN privilege on the table is required anyway; requiring
REPLICATION is user-unfriendly without providing any actual protection.

A related aspect is that the REPLICATION attribute is not needed to
prevent REPACK from stealing slots from logical replication, since
commit e76d8c749c31 made REPACK use a separate pool of replication
slots.

Because there are now successful concurrent repack runs in the
regression tests, we're forced to run test_plan_advice under
wal_level=replica.

Author: Antonin Houska <[email protected]>
Reported-by: Justin Pryzby <[email protected]>
Reviewed-by: Chao Li <[email protected]>
Discussion: https://postgr.es/m/aeJHPNmL4vVy3oPw@pryzbyj2023
---
 src/backend/commands/repack_worker.c          |  1 -
 .../test_plan_advice/t/001_replan_regress.pl  |  1 +
 src/test/regress/expected/cluster.out         | 20 +++++++++++++++++--
 src/test/regress/sql/cluster.sql              | 11 ++++++++--
 4 files changed, 28 insertions(+), 5 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index b17edd771e2..e4a4860805b 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -214,7 +214,6 @@ repack_setup_logical_decoding(Oid relid)
 	/*
 	 * Make sure we can use logical decoding.
 	 */
-	CheckSlotPermissions();
 	CheckLogicalDecodingRequirements(true);
 
 	/*
diff --git a/src/test/modules/test_plan_advice/t/001_replan_regress.pl b/src/test/modules/test_plan_advice/t/001_replan_regress.pl
index 38ffa4d11ae..452b179a665 100644
--- a/src/test/modules/test_plan_advice/t/001_replan_regress.pl
+++ b/src/test/modules/test_plan_advice/t/001_replan_regress.pl
@@ -18,6 +18,7 @@ $node->init();
 # Set up our desired configuration.
 $node->append_conf('postgresql.conf', <<EOM);
 shared_preload_libraries='test_plan_advice'
+wal_level=replica
 pg_plan_advice.always_explain_supplied_advice=false
 pg_plan_advice.feedback_warnings=true
 EOM
diff --git a/src/test/regress/expected/cluster.out b/src/test/regress/expected/cluster.out
index 6127b215a86..e17bc91fae1 100644
--- a/src/test/regress/expected/cluster.out
+++ b/src/test/regress/expected/cluster.out
@@ -543,15 +543,17 @@ ERROR:  REPACK (CONCURRENTLY) is not supported for partitioned tables
 HINT:  Consider running the command on individual partitions.
 DROP TABLE clstrpart;
 -- Ownership of partitions is checked
-CREATE TABLE ptnowner(i int unique) PARTITION BY LIST (i);
+CREATE TABLE ptnowner(i int unique not null) PARTITION BY LIST (i);
 CREATE INDEX ptnowner_i_idx ON ptnowner(i);
 CREATE TABLE ptnowner1 PARTITION OF ptnowner FOR VALUES IN (1);
-CREATE ROLE regress_ptnowner;
+CREATE ROLE regress_ptnowner LOGIN;
 CREATE TABLE ptnowner2 PARTITION OF ptnowner FOR VALUES IN (2);
 ALTER TABLE ptnowner1 OWNER TO regress_ptnowner;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
 ERROR:  permission denied for table ptnowner
+ALTER TABLE ptnowner1 REPLICA IDENTITY USING INDEX ptnowner1_i_key;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
 ALTER TABLE ptnowner OWNER TO regress_ptnowner;
 CREATE TEMP TABLE ptnowner_oldnodes AS
@@ -560,6 +562,11 @@ CREATE TEMP TABLE ptnowner_oldnodes AS
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
 WARNING:  permission denied to execute CLUSTER on "ptnowner2", skipping it
+-- still can't repack without a replica identity
+ALTER TABLE ptnowner1 REPLICA IDENTITY DEFAULT;
+REPACK (CONCURRENTLY) ptnowner1;
+ERROR:  cannot process relation "ptnowner1"
+HINT:  Relation "ptnowner1" has no identity index.
 RESET SESSION AUTHORIZATION;
 SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
   JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
@@ -570,6 +577,15 @@ SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
  ptnowner2 | t
 (3 rows)
 
+SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
+  JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
+  relname  | ?column? 
+-----------+----------
+ ptnowner  | t
+ ptnowner1 | f
+ ptnowner2 | t
+(3 rows)
+
 DROP TABLE ptnowner;
 DROP ROLE regress_ptnowner;
 -- Test CLUSTER with external tuplesorting
diff --git a/src/test/regress/sql/cluster.sql b/src/test/regress/sql/cluster.sql
index d14063a9683..1f471a8821a 100644
--- a/src/test/regress/sql/cluster.sql
+++ b/src/test/regress/sql/cluster.sql
@@ -254,14 +254,16 @@ REPACK (CONCURRENTLY) clstrpart;
 DROP TABLE clstrpart;
 
 -- Ownership of partitions is checked
-CREATE TABLE ptnowner(i int unique) PARTITION BY LIST (i);
+CREATE TABLE ptnowner(i int unique not null) PARTITION BY LIST (i);
 CREATE INDEX ptnowner_i_idx ON ptnowner(i);
 CREATE TABLE ptnowner1 PARTITION OF ptnowner FOR VALUES IN (1);
-CREATE ROLE regress_ptnowner;
+CREATE ROLE regress_ptnowner LOGIN;
 CREATE TABLE ptnowner2 PARTITION OF ptnowner FOR VALUES IN (2);
 ALTER TABLE ptnowner1 OWNER TO regress_ptnowner;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
+ALTER TABLE ptnowner1 REPLICA IDENTITY USING INDEX ptnowner1_i_key;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
 ALTER TABLE ptnowner OWNER TO regress_ptnowner;
 CREATE TEMP TABLE ptnowner_oldnodes AS
@@ -269,7 +271,12 @@ CREATE TEMP TABLE ptnowner_oldnodes AS
   JOIN pg_class AS c ON c.oid=tree.relid;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
+-- still can't repack without a replica identity
+ALTER TABLE ptnowner1 REPLICA IDENTITY DEFAULT;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
+SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
+  JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
 SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
   JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
 DROP TABLE ptnowner;
-- 
2.47.3


--m6zy3l65ushq557m
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-REPACK-do-not-require-LOGIN-privileges.patch"



^ permalink  raw  reply  [nested|flat] 58+ messages in thread

* [PATCH 1/2] REPACK: do not require the user to have REPLICATION
@ 2026-04-20 09:38  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 58+ messages in thread

From: Álvaro Herrera @ 2026-04-20 09:38 UTC (permalink / raw)

Although REPACK (CONCURRENTLY) uses replication slots, there is no
concern that the slot will leak data of other users, because the
MAINTAIN privilege on the table is required anyway; requiring
REPLICATION is user-unfriendly without providing any actual protection.

A related aspect is that the REPLICATION attribute is not needed to
prevent REPACK from stealing slots from logical replication, since
commit e76d8c749c31 made REPACK use a separate pool of replication
slots.

Because there are now successful concurrent repack runs in the
regression tests, we're forced to run test_plan_advice under
wal_level=replica.

Author: Antonin Houska <[email protected]>
Reported-by: Justin Pryzby <[email protected]>
Reviewed-by: Chao Li <[email protected]>
Discussion: https://postgr.es/m/aeJHPNmL4vVy3oPw@pryzbyj2023
---
 src/backend/commands/repack_worker.c          |  1 -
 .../test_plan_advice/t/001_replan_regress.pl  |  1 +
 src/test/regress/expected/cluster.out         | 20 +++++++++++++++++--
 src/test/regress/sql/cluster.sql              | 11 ++++++++--
 4 files changed, 28 insertions(+), 5 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index b17edd771e2..e4a4860805b 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -214,7 +214,6 @@ repack_setup_logical_decoding(Oid relid)
 	/*
 	 * Make sure we can use logical decoding.
 	 */
-	CheckSlotPermissions();
 	CheckLogicalDecodingRequirements(true);
 
 	/*
diff --git a/src/test/modules/test_plan_advice/t/001_replan_regress.pl b/src/test/modules/test_plan_advice/t/001_replan_regress.pl
index 38ffa4d11ae..452b179a665 100644
--- a/src/test/modules/test_plan_advice/t/001_replan_regress.pl
+++ b/src/test/modules/test_plan_advice/t/001_replan_regress.pl
@@ -18,6 +18,7 @@ $node->init();
 # Set up our desired configuration.
 $node->append_conf('postgresql.conf', <<EOM);
 shared_preload_libraries='test_plan_advice'
+wal_level=replica
 pg_plan_advice.always_explain_supplied_advice=false
 pg_plan_advice.feedback_warnings=true
 EOM
diff --git a/src/test/regress/expected/cluster.out b/src/test/regress/expected/cluster.out
index 6127b215a86..e17bc91fae1 100644
--- a/src/test/regress/expected/cluster.out
+++ b/src/test/regress/expected/cluster.out
@@ -543,15 +543,17 @@ ERROR:  REPACK (CONCURRENTLY) is not supported for partitioned tables
 HINT:  Consider running the command on individual partitions.
 DROP TABLE clstrpart;
 -- Ownership of partitions is checked
-CREATE TABLE ptnowner(i int unique) PARTITION BY LIST (i);
+CREATE TABLE ptnowner(i int unique not null) PARTITION BY LIST (i);
 CREATE INDEX ptnowner_i_idx ON ptnowner(i);
 CREATE TABLE ptnowner1 PARTITION OF ptnowner FOR VALUES IN (1);
-CREATE ROLE regress_ptnowner;
+CREATE ROLE regress_ptnowner LOGIN;
 CREATE TABLE ptnowner2 PARTITION OF ptnowner FOR VALUES IN (2);
 ALTER TABLE ptnowner1 OWNER TO regress_ptnowner;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
 ERROR:  permission denied for table ptnowner
+ALTER TABLE ptnowner1 REPLICA IDENTITY USING INDEX ptnowner1_i_key;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
 ALTER TABLE ptnowner OWNER TO regress_ptnowner;
 CREATE TEMP TABLE ptnowner_oldnodes AS
@@ -560,6 +562,11 @@ CREATE TEMP TABLE ptnowner_oldnodes AS
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
 WARNING:  permission denied to execute CLUSTER on "ptnowner2", skipping it
+-- still can't repack without a replica identity
+ALTER TABLE ptnowner1 REPLICA IDENTITY DEFAULT;
+REPACK (CONCURRENTLY) ptnowner1;
+ERROR:  cannot process relation "ptnowner1"
+HINT:  Relation "ptnowner1" has no identity index.
 RESET SESSION AUTHORIZATION;
 SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
   JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
@@ -570,6 +577,15 @@ SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
  ptnowner2 | t
 (3 rows)
 
+SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
+  JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
+  relname  | ?column? 
+-----------+----------
+ ptnowner  | t
+ ptnowner1 | f
+ ptnowner2 | t
+(3 rows)
+
 DROP TABLE ptnowner;
 DROP ROLE regress_ptnowner;
 -- Test CLUSTER with external tuplesorting
diff --git a/src/test/regress/sql/cluster.sql b/src/test/regress/sql/cluster.sql
index d14063a9683..1f471a8821a 100644
--- a/src/test/regress/sql/cluster.sql
+++ b/src/test/regress/sql/cluster.sql
@@ -254,14 +254,16 @@ REPACK (CONCURRENTLY) clstrpart;
 DROP TABLE clstrpart;
 
 -- Ownership of partitions is checked
-CREATE TABLE ptnowner(i int unique) PARTITION BY LIST (i);
+CREATE TABLE ptnowner(i int unique not null) PARTITION BY LIST (i);
 CREATE INDEX ptnowner_i_idx ON ptnowner(i);
 CREATE TABLE ptnowner1 PARTITION OF ptnowner FOR VALUES IN (1);
-CREATE ROLE regress_ptnowner;
+CREATE ROLE regress_ptnowner LOGIN;
 CREATE TABLE ptnowner2 PARTITION OF ptnowner FOR VALUES IN (2);
 ALTER TABLE ptnowner1 OWNER TO regress_ptnowner;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
+ALTER TABLE ptnowner1 REPLICA IDENTITY USING INDEX ptnowner1_i_key;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
 ALTER TABLE ptnowner OWNER TO regress_ptnowner;
 CREATE TEMP TABLE ptnowner_oldnodes AS
@@ -269,7 +271,12 @@ CREATE TEMP TABLE ptnowner_oldnodes AS
   JOIN pg_class AS c ON c.oid=tree.relid;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
+-- still can't repack without a replica identity
+ALTER TABLE ptnowner1 REPLICA IDENTITY DEFAULT;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
+SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
+  JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
 SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
   JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
 DROP TABLE ptnowner;
-- 
2.47.3


--m6zy3l65ushq557m
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-REPACK-do-not-require-LOGIN-privileges.patch"



^ permalink  raw  reply  [nested|flat] 58+ messages in thread

* [PATCH 1/2] REPACK: do not require the user to have REPLICATION
@ 2026-04-20 09:38  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 58+ messages in thread

From: Álvaro Herrera @ 2026-04-20 09:38 UTC (permalink / raw)

Although REPACK (CONCURRENTLY) uses replication slots, there is no
concern that the slot will leak data of other users, because the
MAINTAIN privilege on the table is required anyway; requiring
REPLICATION is user-unfriendly without providing any actual protection.

A related aspect is that the REPLICATION attribute is not needed to
prevent REPACK from stealing slots from logical replication, since
commit e76d8c749c31 made REPACK use a separate pool of replication
slots.

Because there are now successful concurrent repack runs in the
regression tests, we're forced to run test_plan_advice under
wal_level=replica.

Author: Antonin Houska <[email protected]>
Reported-by: Justin Pryzby <[email protected]>
Reviewed-by: Chao Li <[email protected]>
Discussion: https://postgr.es/m/aeJHPNmL4vVy3oPw@pryzbyj2023
---
 src/backend/commands/repack_worker.c          |  1 -
 .../test_plan_advice/t/001_replan_regress.pl  |  1 +
 src/test/regress/expected/cluster.out         | 20 +++++++++++++++++--
 src/test/regress/sql/cluster.sql              | 11 ++++++++--
 4 files changed, 28 insertions(+), 5 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index b17edd771e2..e4a4860805b 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -214,7 +214,6 @@ repack_setup_logical_decoding(Oid relid)
 	/*
 	 * Make sure we can use logical decoding.
 	 */
-	CheckSlotPermissions();
 	CheckLogicalDecodingRequirements(true);
 
 	/*
diff --git a/src/test/modules/test_plan_advice/t/001_replan_regress.pl b/src/test/modules/test_plan_advice/t/001_replan_regress.pl
index 38ffa4d11ae..452b179a665 100644
--- a/src/test/modules/test_plan_advice/t/001_replan_regress.pl
+++ b/src/test/modules/test_plan_advice/t/001_replan_regress.pl
@@ -18,6 +18,7 @@ $node->init();
 # Set up our desired configuration.
 $node->append_conf('postgresql.conf', <<EOM);
 shared_preload_libraries='test_plan_advice'
+wal_level=replica
 pg_plan_advice.always_explain_supplied_advice=false
 pg_plan_advice.feedback_warnings=true
 EOM
diff --git a/src/test/regress/expected/cluster.out b/src/test/regress/expected/cluster.out
index 6127b215a86..e17bc91fae1 100644
--- a/src/test/regress/expected/cluster.out
+++ b/src/test/regress/expected/cluster.out
@@ -543,15 +543,17 @@ ERROR:  REPACK (CONCURRENTLY) is not supported for partitioned tables
 HINT:  Consider running the command on individual partitions.
 DROP TABLE clstrpart;
 -- Ownership of partitions is checked
-CREATE TABLE ptnowner(i int unique) PARTITION BY LIST (i);
+CREATE TABLE ptnowner(i int unique not null) PARTITION BY LIST (i);
 CREATE INDEX ptnowner_i_idx ON ptnowner(i);
 CREATE TABLE ptnowner1 PARTITION OF ptnowner FOR VALUES IN (1);
-CREATE ROLE regress_ptnowner;
+CREATE ROLE regress_ptnowner LOGIN;
 CREATE TABLE ptnowner2 PARTITION OF ptnowner FOR VALUES IN (2);
 ALTER TABLE ptnowner1 OWNER TO regress_ptnowner;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
 ERROR:  permission denied for table ptnowner
+ALTER TABLE ptnowner1 REPLICA IDENTITY USING INDEX ptnowner1_i_key;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
 ALTER TABLE ptnowner OWNER TO regress_ptnowner;
 CREATE TEMP TABLE ptnowner_oldnodes AS
@@ -560,6 +562,11 @@ CREATE TEMP TABLE ptnowner_oldnodes AS
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
 WARNING:  permission denied to execute CLUSTER on "ptnowner2", skipping it
+-- still can't repack without a replica identity
+ALTER TABLE ptnowner1 REPLICA IDENTITY DEFAULT;
+REPACK (CONCURRENTLY) ptnowner1;
+ERROR:  cannot process relation "ptnowner1"
+HINT:  Relation "ptnowner1" has no identity index.
 RESET SESSION AUTHORIZATION;
 SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
   JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
@@ -570,6 +577,15 @@ SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
  ptnowner2 | t
 (3 rows)
 
+SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
+  JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
+  relname  | ?column? 
+-----------+----------
+ ptnowner  | t
+ ptnowner1 | f
+ ptnowner2 | t
+(3 rows)
+
 DROP TABLE ptnowner;
 DROP ROLE regress_ptnowner;
 -- Test CLUSTER with external tuplesorting
diff --git a/src/test/regress/sql/cluster.sql b/src/test/regress/sql/cluster.sql
index d14063a9683..1f471a8821a 100644
--- a/src/test/regress/sql/cluster.sql
+++ b/src/test/regress/sql/cluster.sql
@@ -254,14 +254,16 @@ REPACK (CONCURRENTLY) clstrpart;
 DROP TABLE clstrpart;
 
 -- Ownership of partitions is checked
-CREATE TABLE ptnowner(i int unique) PARTITION BY LIST (i);
+CREATE TABLE ptnowner(i int unique not null) PARTITION BY LIST (i);
 CREATE INDEX ptnowner_i_idx ON ptnowner(i);
 CREATE TABLE ptnowner1 PARTITION OF ptnowner FOR VALUES IN (1);
-CREATE ROLE regress_ptnowner;
+CREATE ROLE regress_ptnowner LOGIN;
 CREATE TABLE ptnowner2 PARTITION OF ptnowner FOR VALUES IN (2);
 ALTER TABLE ptnowner1 OWNER TO regress_ptnowner;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
+ALTER TABLE ptnowner1 REPLICA IDENTITY USING INDEX ptnowner1_i_key;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
 ALTER TABLE ptnowner OWNER TO regress_ptnowner;
 CREATE TEMP TABLE ptnowner_oldnodes AS
@@ -269,7 +271,12 @@ CREATE TEMP TABLE ptnowner_oldnodes AS
   JOIN pg_class AS c ON c.oid=tree.relid;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
+-- still can't repack without a replica identity
+ALTER TABLE ptnowner1 REPLICA IDENTITY DEFAULT;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
+SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
+  JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
 SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
   JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
 DROP TABLE ptnowner;
-- 
2.47.3


--m6zy3l65ushq557m
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-REPACK-do-not-require-LOGIN-privileges.patch"



^ permalink  raw  reply  [nested|flat] 58+ messages in thread

* [PATCH 1/2] REPACK: do not require the user to have REPLICATION
@ 2026-04-20 09:38  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 58+ messages in thread

From: Álvaro Herrera @ 2026-04-20 09:38 UTC (permalink / raw)

Although REPACK (CONCURRENTLY) uses replication slots, there is no
concern that the slot will leak data of other users, because the
MAINTAIN privilege on the table is required anyway; requiring
REPLICATION is user-unfriendly without providing any actual protection.

A related aspect is that the REPLICATION attribute is not needed to
prevent REPACK from stealing slots from logical replication, since
commit e76d8c749c31 made REPACK use a separate pool of replication
slots.

Because there are now successful concurrent repack runs in the
regression tests, we're forced to run test_plan_advice under
wal_level=replica.

Author: Antonin Houska <[email protected]>
Reported-by: Justin Pryzby <[email protected]>
Reviewed-by: Chao Li <[email protected]>
Discussion: https://postgr.es/m/aeJHPNmL4vVy3oPw@pryzbyj2023
---
 src/backend/commands/repack_worker.c          |  1 -
 .../test_plan_advice/t/001_replan_regress.pl  |  1 +
 src/test/regress/expected/cluster.out         | 20 +++++++++++++++++--
 src/test/regress/sql/cluster.sql              | 11 ++++++++--
 4 files changed, 28 insertions(+), 5 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index b17edd771e2..e4a4860805b 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -214,7 +214,6 @@ repack_setup_logical_decoding(Oid relid)
 	/*
 	 * Make sure we can use logical decoding.
 	 */
-	CheckSlotPermissions();
 	CheckLogicalDecodingRequirements(true);
 
 	/*
diff --git a/src/test/modules/test_plan_advice/t/001_replan_regress.pl b/src/test/modules/test_plan_advice/t/001_replan_regress.pl
index 38ffa4d11ae..452b179a665 100644
--- a/src/test/modules/test_plan_advice/t/001_replan_regress.pl
+++ b/src/test/modules/test_plan_advice/t/001_replan_regress.pl
@@ -18,6 +18,7 @@ $node->init();
 # Set up our desired configuration.
 $node->append_conf('postgresql.conf', <<EOM);
 shared_preload_libraries='test_plan_advice'
+wal_level=replica
 pg_plan_advice.always_explain_supplied_advice=false
 pg_plan_advice.feedback_warnings=true
 EOM
diff --git a/src/test/regress/expected/cluster.out b/src/test/regress/expected/cluster.out
index 6127b215a86..e17bc91fae1 100644
--- a/src/test/regress/expected/cluster.out
+++ b/src/test/regress/expected/cluster.out
@@ -543,15 +543,17 @@ ERROR:  REPACK (CONCURRENTLY) is not supported for partitioned tables
 HINT:  Consider running the command on individual partitions.
 DROP TABLE clstrpart;
 -- Ownership of partitions is checked
-CREATE TABLE ptnowner(i int unique) PARTITION BY LIST (i);
+CREATE TABLE ptnowner(i int unique not null) PARTITION BY LIST (i);
 CREATE INDEX ptnowner_i_idx ON ptnowner(i);
 CREATE TABLE ptnowner1 PARTITION OF ptnowner FOR VALUES IN (1);
-CREATE ROLE regress_ptnowner;
+CREATE ROLE regress_ptnowner LOGIN;
 CREATE TABLE ptnowner2 PARTITION OF ptnowner FOR VALUES IN (2);
 ALTER TABLE ptnowner1 OWNER TO regress_ptnowner;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
 ERROR:  permission denied for table ptnowner
+ALTER TABLE ptnowner1 REPLICA IDENTITY USING INDEX ptnowner1_i_key;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
 ALTER TABLE ptnowner OWNER TO regress_ptnowner;
 CREATE TEMP TABLE ptnowner_oldnodes AS
@@ -560,6 +562,11 @@ CREATE TEMP TABLE ptnowner_oldnodes AS
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
 WARNING:  permission denied to execute CLUSTER on "ptnowner2", skipping it
+-- still can't repack without a replica identity
+ALTER TABLE ptnowner1 REPLICA IDENTITY DEFAULT;
+REPACK (CONCURRENTLY) ptnowner1;
+ERROR:  cannot process relation "ptnowner1"
+HINT:  Relation "ptnowner1" has no identity index.
 RESET SESSION AUTHORIZATION;
 SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
   JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
@@ -570,6 +577,15 @@ SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
  ptnowner2 | t
 (3 rows)
 
+SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
+  JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
+  relname  | ?column? 
+-----------+----------
+ ptnowner  | t
+ ptnowner1 | f
+ ptnowner2 | t
+(3 rows)
+
 DROP TABLE ptnowner;
 DROP ROLE regress_ptnowner;
 -- Test CLUSTER with external tuplesorting
diff --git a/src/test/regress/sql/cluster.sql b/src/test/regress/sql/cluster.sql
index d14063a9683..1f471a8821a 100644
--- a/src/test/regress/sql/cluster.sql
+++ b/src/test/regress/sql/cluster.sql
@@ -254,14 +254,16 @@ REPACK (CONCURRENTLY) clstrpart;
 DROP TABLE clstrpart;
 
 -- Ownership of partitions is checked
-CREATE TABLE ptnowner(i int unique) PARTITION BY LIST (i);
+CREATE TABLE ptnowner(i int unique not null) PARTITION BY LIST (i);
 CREATE INDEX ptnowner_i_idx ON ptnowner(i);
 CREATE TABLE ptnowner1 PARTITION OF ptnowner FOR VALUES IN (1);
-CREATE ROLE regress_ptnowner;
+CREATE ROLE regress_ptnowner LOGIN;
 CREATE TABLE ptnowner2 PARTITION OF ptnowner FOR VALUES IN (2);
 ALTER TABLE ptnowner1 OWNER TO regress_ptnowner;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
+ALTER TABLE ptnowner1 REPLICA IDENTITY USING INDEX ptnowner1_i_key;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
 ALTER TABLE ptnowner OWNER TO regress_ptnowner;
 CREATE TEMP TABLE ptnowner_oldnodes AS
@@ -269,7 +271,12 @@ CREATE TEMP TABLE ptnowner_oldnodes AS
   JOIN pg_class AS c ON c.oid=tree.relid;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
+-- still can't repack without a replica identity
+ALTER TABLE ptnowner1 REPLICA IDENTITY DEFAULT;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
+SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
+  JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
 SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
   JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
 DROP TABLE ptnowner;
-- 
2.47.3


--m6zy3l65ushq557m
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-REPACK-do-not-require-LOGIN-privileges.patch"



^ permalink  raw  reply  [nested|flat] 58+ messages in thread

* [PATCH 1/2] REPACK: do not require the user to have REPLICATION
@ 2026-04-20 09:38  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 58+ messages in thread

From: Álvaro Herrera @ 2026-04-20 09:38 UTC (permalink / raw)

Although REPACK (CONCURRENTLY) uses replication slots, there is no
concern that the slot will leak data of other users, because the
MAINTAIN privilege on the table is required anyway; requiring
REPLICATION is user-unfriendly without providing any actual protection.

A related aspect is that the REPLICATION attribute is not needed to
prevent REPACK from stealing slots from logical replication, since
commit e76d8c749c31 made REPACK use a separate pool of replication
slots.

Because there are now successful concurrent repack runs in the
regression tests, we're forced to run test_plan_advice under
wal_level=replica.

Author: Antonin Houska <[email protected]>
Reported-by: Justin Pryzby <[email protected]>
Reviewed-by: Chao Li <[email protected]>
Discussion: https://postgr.es/m/aeJHPNmL4vVy3oPw@pryzbyj2023
---
 src/backend/commands/repack_worker.c          |  1 -
 .../test_plan_advice/t/001_replan_regress.pl  |  1 +
 src/test/regress/expected/cluster.out         | 20 +++++++++++++++++--
 src/test/regress/sql/cluster.sql              | 11 ++++++++--
 4 files changed, 28 insertions(+), 5 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index b17edd771e2..e4a4860805b 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -214,7 +214,6 @@ repack_setup_logical_decoding(Oid relid)
 	/*
 	 * Make sure we can use logical decoding.
 	 */
-	CheckSlotPermissions();
 	CheckLogicalDecodingRequirements(true);
 
 	/*
diff --git a/src/test/modules/test_plan_advice/t/001_replan_regress.pl b/src/test/modules/test_plan_advice/t/001_replan_regress.pl
index 38ffa4d11ae..452b179a665 100644
--- a/src/test/modules/test_plan_advice/t/001_replan_regress.pl
+++ b/src/test/modules/test_plan_advice/t/001_replan_regress.pl
@@ -18,6 +18,7 @@ $node->init();
 # Set up our desired configuration.
 $node->append_conf('postgresql.conf', <<EOM);
 shared_preload_libraries='test_plan_advice'
+wal_level=replica
 pg_plan_advice.always_explain_supplied_advice=false
 pg_plan_advice.feedback_warnings=true
 EOM
diff --git a/src/test/regress/expected/cluster.out b/src/test/regress/expected/cluster.out
index 6127b215a86..e17bc91fae1 100644
--- a/src/test/regress/expected/cluster.out
+++ b/src/test/regress/expected/cluster.out
@@ -543,15 +543,17 @@ ERROR:  REPACK (CONCURRENTLY) is not supported for partitioned tables
 HINT:  Consider running the command on individual partitions.
 DROP TABLE clstrpart;
 -- Ownership of partitions is checked
-CREATE TABLE ptnowner(i int unique) PARTITION BY LIST (i);
+CREATE TABLE ptnowner(i int unique not null) PARTITION BY LIST (i);
 CREATE INDEX ptnowner_i_idx ON ptnowner(i);
 CREATE TABLE ptnowner1 PARTITION OF ptnowner FOR VALUES IN (1);
-CREATE ROLE regress_ptnowner;
+CREATE ROLE regress_ptnowner LOGIN;
 CREATE TABLE ptnowner2 PARTITION OF ptnowner FOR VALUES IN (2);
 ALTER TABLE ptnowner1 OWNER TO regress_ptnowner;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
 ERROR:  permission denied for table ptnowner
+ALTER TABLE ptnowner1 REPLICA IDENTITY USING INDEX ptnowner1_i_key;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
 ALTER TABLE ptnowner OWNER TO regress_ptnowner;
 CREATE TEMP TABLE ptnowner_oldnodes AS
@@ -560,6 +562,11 @@ CREATE TEMP TABLE ptnowner_oldnodes AS
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
 WARNING:  permission denied to execute CLUSTER on "ptnowner2", skipping it
+-- still can't repack without a replica identity
+ALTER TABLE ptnowner1 REPLICA IDENTITY DEFAULT;
+REPACK (CONCURRENTLY) ptnowner1;
+ERROR:  cannot process relation "ptnowner1"
+HINT:  Relation "ptnowner1" has no identity index.
 RESET SESSION AUTHORIZATION;
 SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
   JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
@@ -570,6 +577,15 @@ SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
  ptnowner2 | t
 (3 rows)
 
+SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
+  JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
+  relname  | ?column? 
+-----------+----------
+ ptnowner  | t
+ ptnowner1 | f
+ ptnowner2 | t
+(3 rows)
+
 DROP TABLE ptnowner;
 DROP ROLE regress_ptnowner;
 -- Test CLUSTER with external tuplesorting
diff --git a/src/test/regress/sql/cluster.sql b/src/test/regress/sql/cluster.sql
index d14063a9683..1f471a8821a 100644
--- a/src/test/regress/sql/cluster.sql
+++ b/src/test/regress/sql/cluster.sql
@@ -254,14 +254,16 @@ REPACK (CONCURRENTLY) clstrpart;
 DROP TABLE clstrpart;
 
 -- Ownership of partitions is checked
-CREATE TABLE ptnowner(i int unique) PARTITION BY LIST (i);
+CREATE TABLE ptnowner(i int unique not null) PARTITION BY LIST (i);
 CREATE INDEX ptnowner_i_idx ON ptnowner(i);
 CREATE TABLE ptnowner1 PARTITION OF ptnowner FOR VALUES IN (1);
-CREATE ROLE regress_ptnowner;
+CREATE ROLE regress_ptnowner LOGIN;
 CREATE TABLE ptnowner2 PARTITION OF ptnowner FOR VALUES IN (2);
 ALTER TABLE ptnowner1 OWNER TO regress_ptnowner;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
+ALTER TABLE ptnowner1 REPLICA IDENTITY USING INDEX ptnowner1_i_key;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
 ALTER TABLE ptnowner OWNER TO regress_ptnowner;
 CREATE TEMP TABLE ptnowner_oldnodes AS
@@ -269,7 +271,12 @@ CREATE TEMP TABLE ptnowner_oldnodes AS
   JOIN pg_class AS c ON c.oid=tree.relid;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
+-- still can't repack without a replica identity
+ALTER TABLE ptnowner1 REPLICA IDENTITY DEFAULT;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
+SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
+  JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
 SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
   JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
 DROP TABLE ptnowner;
-- 
2.47.3


--m6zy3l65ushq557m
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-REPACK-do-not-require-LOGIN-privileges.patch"



^ permalink  raw  reply  [nested|flat] 58+ messages in thread

* [PATCH 1/2] REPACK: do not require the user to have REPLICATION
@ 2026-04-20 09:38  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 58+ messages in thread

From: Álvaro Herrera @ 2026-04-20 09:38 UTC (permalink / raw)

Although REPACK (CONCURRENTLY) uses replication slots, there is no
concern that the slot will leak data of other users, because the
MAINTAIN privilege on the table is required anyway; requiring
REPLICATION is user-unfriendly without providing any actual protection.

A related aspect is that the REPLICATION attribute is not needed to
prevent REPACK from stealing slots from logical replication, since
commit e76d8c749c31 made REPACK use a separate pool of replication
slots.

Because there are now successful concurrent repack runs in the
regression tests, we're forced to run test_plan_advice under
wal_level=replica.

Author: Antonin Houska <[email protected]>
Reported-by: Justin Pryzby <[email protected]>
Reviewed-by: Chao Li <[email protected]>
Discussion: https://postgr.es/m/aeJHPNmL4vVy3oPw@pryzbyj2023
---
 src/backend/commands/repack_worker.c          |  1 -
 .../test_plan_advice/t/001_replan_regress.pl  |  1 +
 src/test/regress/expected/cluster.out         | 20 +++++++++++++++++--
 src/test/regress/sql/cluster.sql              | 11 ++++++++--
 4 files changed, 28 insertions(+), 5 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index b17edd771e2..e4a4860805b 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -214,7 +214,6 @@ repack_setup_logical_decoding(Oid relid)
 	/*
 	 * Make sure we can use logical decoding.
 	 */
-	CheckSlotPermissions();
 	CheckLogicalDecodingRequirements(true);
 
 	/*
diff --git a/src/test/modules/test_plan_advice/t/001_replan_regress.pl b/src/test/modules/test_plan_advice/t/001_replan_regress.pl
index 38ffa4d11ae..452b179a665 100644
--- a/src/test/modules/test_plan_advice/t/001_replan_regress.pl
+++ b/src/test/modules/test_plan_advice/t/001_replan_regress.pl
@@ -18,6 +18,7 @@ $node->init();
 # Set up our desired configuration.
 $node->append_conf('postgresql.conf', <<EOM);
 shared_preload_libraries='test_plan_advice'
+wal_level=replica
 pg_plan_advice.always_explain_supplied_advice=false
 pg_plan_advice.feedback_warnings=true
 EOM
diff --git a/src/test/regress/expected/cluster.out b/src/test/regress/expected/cluster.out
index 6127b215a86..e17bc91fae1 100644
--- a/src/test/regress/expected/cluster.out
+++ b/src/test/regress/expected/cluster.out
@@ -543,15 +543,17 @@ ERROR:  REPACK (CONCURRENTLY) is not supported for partitioned tables
 HINT:  Consider running the command on individual partitions.
 DROP TABLE clstrpart;
 -- Ownership of partitions is checked
-CREATE TABLE ptnowner(i int unique) PARTITION BY LIST (i);
+CREATE TABLE ptnowner(i int unique not null) PARTITION BY LIST (i);
 CREATE INDEX ptnowner_i_idx ON ptnowner(i);
 CREATE TABLE ptnowner1 PARTITION OF ptnowner FOR VALUES IN (1);
-CREATE ROLE regress_ptnowner;
+CREATE ROLE regress_ptnowner LOGIN;
 CREATE TABLE ptnowner2 PARTITION OF ptnowner FOR VALUES IN (2);
 ALTER TABLE ptnowner1 OWNER TO regress_ptnowner;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
 ERROR:  permission denied for table ptnowner
+ALTER TABLE ptnowner1 REPLICA IDENTITY USING INDEX ptnowner1_i_key;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
 ALTER TABLE ptnowner OWNER TO regress_ptnowner;
 CREATE TEMP TABLE ptnowner_oldnodes AS
@@ -560,6 +562,11 @@ CREATE TEMP TABLE ptnowner_oldnodes AS
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
 WARNING:  permission denied to execute CLUSTER on "ptnowner2", skipping it
+-- still can't repack without a replica identity
+ALTER TABLE ptnowner1 REPLICA IDENTITY DEFAULT;
+REPACK (CONCURRENTLY) ptnowner1;
+ERROR:  cannot process relation "ptnowner1"
+HINT:  Relation "ptnowner1" has no identity index.
 RESET SESSION AUTHORIZATION;
 SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
   JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
@@ -570,6 +577,15 @@ SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
  ptnowner2 | t
 (3 rows)
 
+SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
+  JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
+  relname  | ?column? 
+-----------+----------
+ ptnowner  | t
+ ptnowner1 | f
+ ptnowner2 | t
+(3 rows)
+
 DROP TABLE ptnowner;
 DROP ROLE regress_ptnowner;
 -- Test CLUSTER with external tuplesorting
diff --git a/src/test/regress/sql/cluster.sql b/src/test/regress/sql/cluster.sql
index d14063a9683..1f471a8821a 100644
--- a/src/test/regress/sql/cluster.sql
+++ b/src/test/regress/sql/cluster.sql
@@ -254,14 +254,16 @@ REPACK (CONCURRENTLY) clstrpart;
 DROP TABLE clstrpart;
 
 -- Ownership of partitions is checked
-CREATE TABLE ptnowner(i int unique) PARTITION BY LIST (i);
+CREATE TABLE ptnowner(i int unique not null) PARTITION BY LIST (i);
 CREATE INDEX ptnowner_i_idx ON ptnowner(i);
 CREATE TABLE ptnowner1 PARTITION OF ptnowner FOR VALUES IN (1);
-CREATE ROLE regress_ptnowner;
+CREATE ROLE regress_ptnowner LOGIN;
 CREATE TABLE ptnowner2 PARTITION OF ptnowner FOR VALUES IN (2);
 ALTER TABLE ptnowner1 OWNER TO regress_ptnowner;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
+ALTER TABLE ptnowner1 REPLICA IDENTITY USING INDEX ptnowner1_i_key;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
 ALTER TABLE ptnowner OWNER TO regress_ptnowner;
 CREATE TEMP TABLE ptnowner_oldnodes AS
@@ -269,7 +271,12 @@ CREATE TEMP TABLE ptnowner_oldnodes AS
   JOIN pg_class AS c ON c.oid=tree.relid;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
+-- still can't repack without a replica identity
+ALTER TABLE ptnowner1 REPLICA IDENTITY DEFAULT;
+REPACK (CONCURRENTLY) ptnowner1;
 RESET SESSION AUTHORIZATION;
+SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
+  JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
 SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
   JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
 DROP TABLE ptnowner;
-- 
2.47.3


--m6zy3l65ushq557m
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0002-REPACK-do-not-require-LOGIN-privileges.patch"



^ permalink  raw  reply  [nested|flat] 58+ messages in thread

* [PATCH v5] Handle element label and element label property objects in object address functions
@ 2026-04-23 09:27  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 58+ messages in thread

From: Bertrand Drouvot @ 2026-04-23 09:27 UTC (permalink / raw)

getObjectTypeDescription() and getObjectIdentityParts() do not handle objects in
pg_propgraph_element_label and pg_propgraph_label_property catalogs. These
functions when called for handling a DDL that affects these objects causes
"unsupported object class" error. An error is reported when these functions are
called when pg_identify_object() and pg_identify_object_as_address() are invoked
with objects from the said catalogs.

The objects in these catalogs do not have a (user given) name but they can be
manipulated invdividually through ALTER PROPERTY GRAPH sub-commands. Hence they
need to be accessible to the event triggers. Handle these catalogs in the
respective functions.

In passing, replace manual scans in getObjectDescription() to be consistent with
the ones added in getObjectIdentityParts().

Reported-by: Bertrand Drouvot <[email protected]>
Author: Bertrand Drouvot <[email protected]>
Author: Ashutosh Bapat <[email protected]>
Reviewed-by: Michael Paquier <[email protected]>
Reviewed-by: Ashutosh Bapat <[email protected]>
Discussion: https://postgr.es/m/aej1DkLwhyZWmtxJ@bdtpg
---
 src/backend/catalog/objectaddress.c           | 111 ++++++++--
 .../expected/create_property_graph.out        | 203 ++++++++++--------
 src/test/regress/expected/event_trigger.out   |  22 ++
 src/test/regress/expected/object_address.out  |   9 +-
 .../regress/sql/create_property_graph.sql     |  36 ++--
 src/test/regress/sql/event_trigger.sql        |  15 ++
 src/test/regress/sql/object_address.sql       |   5 +-
 7 files changed, 279 insertions(+), 122 deletions(-)
  15.2% src/backend/catalog/
  74.2% src/test/regress/expected/
  10.5% src/test/regress/sql/

diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index c1862809577..70058522b35 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -4106,26 +4106,19 @@ getObjectDescription(const ObjectAddress *object, bool missing_ok)
 		case PropgraphElementLabelRelationId:
 			{
 				Relation	rel;
-				SysScanDesc scan;
-				ScanKeyData key[1];
 				HeapTuple	tuple;
 				Form_pg_propgraph_element_label pgelform;
 				ObjectAddress oa;
 
 				rel = table_open(PropgraphElementLabelRelationId, AccessShareLock);
-				ScanKeyInit(&key[0],
-							Anum_pg_propgraph_element_label_oid,
-							BTEqualStrategyNumber, F_OIDEQ,
-							ObjectIdGetDatum(object->objectId));
-
-				scan = systable_beginscan(rel, PropgraphElementLabelObjectIndexId, true, NULL, 1, key);
-				tuple = systable_getnext(scan);
+				tuple = get_catalog_object_by_oid(rel,
+												  Anum_pg_propgraph_element_label_oid,
+												  object->objectId);
 				if (!HeapTupleIsValid(tuple))
 				{
 					if (!missing_ok)
 						elog(ERROR, "could not find tuple for element label %u", object->objectId);
 
-					systable_endscan(scan);
 					table_close(rel, AccessShareLock);
 					break;
 				}
@@ -4136,7 +4129,6 @@ getObjectDescription(const ObjectAddress *object, bool missing_ok)
 				ObjectAddressSet(oa, PropgraphElementRelationId, pgelform->pgelelid);
 				appendStringInfoString(&buffer, getObjectDescription(&oa, false));
 
-				systable_endscan(scan);
 				table_close(rel, AccessShareLock);
 				break;
 			}
@@ -4166,26 +4158,19 @@ getObjectDescription(const ObjectAddress *object, bool missing_ok)
 		case PropgraphLabelPropertyRelationId:
 			{
 				Relation	rel;
-				SysScanDesc scan;
-				ScanKeyData key[1];
 				HeapTuple	tuple;
 				Form_pg_propgraph_label_property plpform;
 				ObjectAddress oa;
 
 				rel = table_open(PropgraphLabelPropertyRelationId, AccessShareLock);
-				ScanKeyInit(&key[0],
-							Anum_pg_propgraph_label_property_oid,
-							BTEqualStrategyNumber, F_OIDEQ,
-							ObjectIdGetDatum(object->objectId));
-
-				scan = systable_beginscan(rel, PropgraphLabelPropertyObjectIndexId, true, NULL, 1, key);
-				tuple = systable_getnext(scan);
+				tuple = get_catalog_object_by_oid(rel,
+												  Anum_pg_propgraph_label_property_oid,
+												  object->objectId);
 				if (!HeapTupleIsValid(tuple))
 				{
 					if (!missing_ok)
 						elog(ERROR, "could not find tuple for label property %u", object->objectId);
 
-					systable_endscan(scan);
 					table_close(rel, AccessShareLock);
 					break;
 				}
@@ -4196,7 +4181,6 @@ getObjectDescription(const ObjectAddress *object, bool missing_ok)
 				ObjectAddressSet(oa, PropgraphElementLabelRelationId, plpform->plpellabelid);
 				appendStringInfoString(&buffer, getObjectDescription(&oa, false));
 
-				systable_endscan(scan);
 				table_close(rel, AccessShareLock);
 				break;
 			}
@@ -4913,6 +4897,14 @@ getObjectTypeDescription(const ObjectAddress *object, bool missing_ok)
 			appendStringInfoString(&buffer, "property graph property");
 			break;
 
+		case PropgraphElementLabelRelationId:
+			appendStringInfoString(&buffer, "property graph element label");
+			break;
+
+		case PropgraphLabelPropertyRelationId:
+			appendStringInfoString(&buffer, "property graph element label property");
+			break;
+
 		case PublicationRelationId:
 			appendStringInfoString(&buffer, "publication");
 			break;
@@ -6228,6 +6220,81 @@ getObjectIdentityParts(const ObjectAddress *object,
 				break;
 			}
 
+		case PropgraphElementLabelRelationId:
+			{
+				Relation	ellabelDesc;
+				HeapTuple	tup;
+				Form_pg_propgraph_element_label pgelform;
+				ObjectAddress oa;
+				char	   *labelname;
+
+				ellabelDesc = table_open(PropgraphElementLabelRelationId, AccessShareLock);
+				tup = get_catalog_object_by_oid(ellabelDesc,
+												Anum_pg_propgraph_element_label_oid,
+												object->objectId);
+				if (!HeapTupleIsValid(tup))
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for element label %u",
+							 object->objectId);
+
+					table_close(ellabelDesc, AccessShareLock);
+					break;
+				}
+
+				pgelform = (Form_pg_propgraph_element_label) GETSTRUCT(tup);
+
+				labelname = get_propgraph_label_name(pgelform->pgellabelid);
+				appendStringInfo(&buffer, _("%s of "), quote_identifier(labelname));
+				ObjectAddressSet(oa, PropgraphElementRelationId, pgelform->pgelelid);
+				appendStringInfoString(&buffer, getObjectIdentityParts(&oa, objname,
+																	   objargs, false));
+				/* labelname is already pstrdup'ed. */
+				if (objname)
+					*objname = lappend(*objname, labelname);
+
+				table_close(ellabelDesc, AccessShareLock);
+				break;
+			}
+
+		case PropgraphLabelPropertyRelationId:
+			{
+				Relation	lblpropDesc;
+				HeapTuple	tup;
+				Form_pg_propgraph_label_property plpform;
+				ObjectAddress oa;
+				char	   *propname;
+
+				lblpropDesc = table_open(PropgraphLabelPropertyRelationId,
+										 AccessShareLock);
+				tup = get_catalog_object_by_oid(lblpropDesc,
+												Anum_pg_propgraph_label_property_oid,
+												object->objectId);
+				if (!HeapTupleIsValid(tup))
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for label property %u",
+							 object->objectId);
+
+					table_close(lblpropDesc, AccessShareLock);
+					break;
+				}
+
+				plpform = (Form_pg_propgraph_label_property) GETSTRUCT(tup);
+
+				propname = get_propgraph_property_name(plpform->plppropid);
+				appendStringInfo(&buffer, _("%s of "), quote_identifier(propname));
+				ObjectAddressSet(oa, PropgraphElementLabelRelationId, plpform->plpellabelid);
+				appendStringInfoString(&buffer, getObjectIdentityParts(&oa, objname,
+																	   objargs, false));
+				/* propname is already pstrdup'ed. */
+				if (objname)
+					*objname = lappend(*objname, propname);
+
+				table_close(lblpropDesc, AccessShareLock);
+				break;
+			}
+
 		case PublicationRelationId:
 			{
 				char	   *pubname;
diff --git a/src/test/regress/expected/create_property_graph.out b/src/test/regress/expected/create_property_graph.out
index bc9a596ec89..2e77e4503ae 100644
--- a/src/test/regress/expected/create_property_graph.out
+++ b/src/test/regress/expected/create_property_graph.out
@@ -661,96 +661,129 @@ SELECT * FROM information_schema.pg_property_graph_privileges WHERE grantee LIKE
 (2 rows)
 
 -- test object address functions
+CREATE TEMPORARY VIEW deps_tree AS
+    WITH RECURSIVE deps (classid, objid, objsubid, refclassid, refobjid, refobjsubid) AS (
+        SELECT classid, objid, objsubid,
+               refclassid, refobjid, refobjsubid
+        FROM pg_depend
+        WHERE refclassid = 'pg_class'::regclass AND
+              refobjid = 'create_property_graph_tests.gt'::regclass AND
+        -- eliminate this view, which is not a real dependency, from the result
+              classid <> 'pg_rewrite'::regclass
+        UNION ALL
+        SELECT d.classid, d.objid, d.objsubid,
+               d.refclassid, d.refobjid, d.refobjsubid
+        FROM pg_depend d
+        JOIN deps dp ON d.refclassid = dp.classid AND d.refobjid = dp.objid AND d.refobjsubid = dp.objsubid
+    ) SELECT DISTINCT * FROM deps;
 SELECT pg_describe_object(classid, objid, objsubid) as obj,
-       pg_describe_object(refclassid, refobjid, refobjsubid) as reference_graph
-    FROM pg_depend
-    WHERE refclassid = 'pg_class'::regclass AND
-          refobjid = 'create_property_graph_tests.g2'::regclass
-    ORDER BY 1, 2;
-               obj               |  reference_graph  
----------------------------------+-------------------
- edge e1 of property graph g2    | property graph g2
- edge e2 of property graph g2    | property graph g2
- label e1 of property graph g2   | property graph g2
- label e2 of property graph g2   | property graph g2
- label t1 of property graph g2   | property graph g2
- label t2 of property graph g2   | property graph g2
- label t3l1 of property graph g2 | property graph g2
- label t3l2 of property graph g2 | property graph g2
- property a of property graph g2 | property graph g2
- property b of property graph g2 | property graph g2
- property i of property graph g2 | property graph g2
- property j of property graph g2 | property graph g2
- property k of property graph g2 | property graph g2
- property t of property graph g2 | property graph g2
- property x of property graph g2 | property graph g2
- property y of property graph g2 | property graph g2
- property z of property graph g2 | property graph g2
- type g2                         | property graph g2
- vertex t1 of property graph g2  | property graph g2
- vertex t2 of property graph g2  | property graph g2
- vertex t3 of property graph g2  | property graph g2
-(21 rows)
+       pg_describe_object(refclassid, refobjid, refobjsubid) as refobj
+    FROM deps_tree ORDER BY 1, 2;
+                           obj                            |                   refobj                   
+----------------------------------------------------------+--------------------------------------------
+ edge e of property graph gt                              | property graph gt
+ edge e of property graph gt                              | vertex v1 of property graph gt
+ edge e of property graph gt                              | vertex v2 of property graph gt
+ label e of edge e of property graph gt                   | edge e of property graph gt
+ label e of edge e of property graph gt                   | label e of property graph gt
+ label e of property graph gt                             | property graph gt
+ label v1 of property graph gt                            | property graph gt
+ label v1 of vertex v1 of property graph gt               | label v1 of property graph gt
+ label v1 of vertex v1 of property graph gt               | vertex v1 of property graph gt
+ label v2 of property graph gt                            | property graph gt
+ label v2 of vertex v2 of property graph gt               | label v2 of property graph gt
+ label v2 of vertex v2 of property graph gt               | vertex v2 of property graph gt
+ property a of label v1 of vertex v1 of property graph gt | label v1 of vertex v1 of property graph gt
+ property a of label v1 of vertex v1 of property graph gt | property a of property graph gt
+ property a of property graph gt                          | property graph gt
+ property b of label v1 of vertex v1 of property graph gt | label v1 of vertex v1 of property graph gt
+ property b of label v1 of vertex v1 of property graph gt | property b of property graph gt
+ property b of property graph gt                          | property graph gt
+ property c of label e of edge e of property graph gt     | label e of edge e of property graph gt
+ property c of label e of edge e of property graph gt     | property c of property graph gt
+ property c of property graph gt                          | property graph gt
+ property k1 of label e of edge e of property graph gt    | label e of edge e of property graph gt
+ property k1 of label e of edge e of property graph gt    | property k1 of property graph gt
+ property k1 of property graph gt                         | property graph gt
+ property k2 of label e of edge e of property graph gt    | label e of edge e of property graph gt
+ property k2 of label e of edge e of property graph gt    | property k2 of property graph gt
+ property k2 of property graph gt                         | property graph gt
+ property m of label v2 of vertex v2 of property graph gt | label v2 of vertex v2 of property graph gt
+ property m of label v2 of vertex v2 of property graph gt | property m of property graph gt
+ property m of property graph gt                          | property graph gt
+ property n of label v2 of vertex v2 of property graph gt | label v2 of vertex v2 of property graph gt
+ property n of label v2 of vertex v2 of property graph gt | property n of property graph gt
+ property n of property graph gt                          | property graph gt
+ type gt                                                  | property graph gt
+ type gt[]                                                | type gt
+ vertex v1 of property graph gt                           | property graph gt
+ vertex v2 of property graph gt                           | property graph gt
+(37 rows)
 
 SELECT (pg_identify_object_as_address(classid, objid, objsubid)).*
-    FROM pg_depend
-    WHERE refclassid = 'pg_class'::regclass AND
-          refobjid = 'create_property_graph_tests.g2'::regclass
+    FROM (SELECT DISTINCT classid, objid, objsubid FROM deps_tree)
     ORDER BY 1, 2, 3;
-          type           |             object_names              | object_args 
--------------------------+---------------------------------------+-------------
- property graph element  | {create_property_graph_tests,g2,e1}   | {}
- property graph element  | {create_property_graph_tests,g2,e2}   | {}
- property graph element  | {create_property_graph_tests,g2,t1}   | {}
- property graph element  | {create_property_graph_tests,g2,t2}   | {}
- property graph element  | {create_property_graph_tests,g2,t3}   | {}
- property graph label    | {create_property_graph_tests,g2,e1}   | {}
- property graph label    | {create_property_graph_tests,g2,e2}   | {}
- property graph label    | {create_property_graph_tests,g2,t1}   | {}
- property graph label    | {create_property_graph_tests,g2,t2}   | {}
- property graph label    | {create_property_graph_tests,g2,t3l1} | {}
- property graph label    | {create_property_graph_tests,g2,t3l2} | {}
- property graph property | {create_property_graph_tests,g2,a}    | {}
- property graph property | {create_property_graph_tests,g2,b}    | {}
- property graph property | {create_property_graph_tests,g2,i}    | {}
- property graph property | {create_property_graph_tests,g2,j}    | {}
- property graph property | {create_property_graph_tests,g2,k}    | {}
- property graph property | {create_property_graph_tests,g2,t}    | {}
- property graph property | {create_property_graph_tests,g2,x}    | {}
- property graph property | {create_property_graph_tests,g2,y}    | {}
- property graph property | {create_property_graph_tests,g2,z}    | {}
- type                    | {create_property_graph_tests.g2}      | {}
-(21 rows)
+                 type                  |               object_names               | object_args 
+---------------------------------------+------------------------------------------+-------------
+ property graph element                | {create_property_graph_tests,gt,e}       | {}
+ property graph element                | {create_property_graph_tests,gt,v1}      | {}
+ property graph element                | {create_property_graph_tests,gt,v2}      | {}
+ property graph element label          | {create_property_graph_tests,gt,e,e}     | {}
+ property graph element label          | {create_property_graph_tests,gt,v1,v1}   | {}
+ property graph element label          | {create_property_graph_tests,gt,v2,v2}   | {}
+ property graph element label property | {create_property_graph_tests,gt,e,e,c}   | {}
+ property graph element label property | {create_property_graph_tests,gt,e,e,k1}  | {}
+ property graph element label property | {create_property_graph_tests,gt,e,e,k2}  | {}
+ property graph element label property | {create_property_graph_tests,gt,v1,v1,a} | {}
+ property graph element label property | {create_property_graph_tests,gt,v1,v1,b} | {}
+ property graph element label property | {create_property_graph_tests,gt,v2,v2,m} | {}
+ property graph element label property | {create_property_graph_tests,gt,v2,v2,n} | {}
+ property graph label                  | {create_property_graph_tests,gt,e}       | {}
+ property graph label                  | {create_property_graph_tests,gt,v1}      | {}
+ property graph label                  | {create_property_graph_tests,gt,v2}      | {}
+ property graph property               | {create_property_graph_tests,gt,a}       | {}
+ property graph property               | {create_property_graph_tests,gt,b}       | {}
+ property graph property               | {create_property_graph_tests,gt,c}       | {}
+ property graph property               | {create_property_graph_tests,gt,k1}      | {}
+ property graph property               | {create_property_graph_tests,gt,k2}      | {}
+ property graph property               | {create_property_graph_tests,gt,m}       | {}
+ property graph property               | {create_property_graph_tests,gt,n}       | {}
+ type                                  | {create_property_graph_tests.gt}         | {}
+ type                                  | {create_property_graph_tests.gt[]}       | {}
+(25 rows)
 
-SELECT (pg_identify_object(classid, objid, objsubid)).*
-    FROM pg_depend
-    WHERE refclassid = 'pg_class'::regclass AND
-          refobjid = 'create_property_graph_tests.g2'::regclass
-    ORDER BY 1, 2, 3, 4;
-          type           |           schema            | name |                identity                
--------------------------+-----------------------------+------+----------------------------------------
- property graph element  |                             |      | e1 of create_property_graph_tests.g2
- property graph element  |                             |      | e2 of create_property_graph_tests.g2
- property graph element  |                             |      | t1 of create_property_graph_tests.g2
- property graph element  |                             |      | t2 of create_property_graph_tests.g2
- property graph element  |                             |      | t3 of create_property_graph_tests.g2
- property graph label    |                             |      | e1 of create_property_graph_tests.g2
- property graph label    |                             |      | e2 of create_property_graph_tests.g2
- property graph label    |                             |      | t1 of create_property_graph_tests.g2
- property graph label    |                             |      | t2 of create_property_graph_tests.g2
- property graph label    |                             |      | t3l1 of create_property_graph_tests.g2
- property graph label    |                             |      | t3l2 of create_property_graph_tests.g2
- property graph property |                             |      | a of create_property_graph_tests.g2
- property graph property |                             |      | b of create_property_graph_tests.g2
- property graph property |                             |      | i of create_property_graph_tests.g2
- property graph property |                             |      | j of create_property_graph_tests.g2
- property graph property |                             |      | k of create_property_graph_tests.g2
- property graph property |                             |      | t of create_property_graph_tests.g2
- property graph property |                             |      | x of create_property_graph_tests.g2
- property graph property |                             |      | y of create_property_graph_tests.g2
- property graph property |                             |      | z of create_property_graph_tests.g2
- type                    | create_property_graph_tests | g2   | create_property_graph_tests.g2
-(21 rows)
+SELECT *
+    FROM (SELECT (pg_identify_object(classid, objid, objsubid)).*
+          FROM (SELECT DISTINCT classid, objid, objsubid FROM deps_tree)) s
+    ORDER BY type, schema, name COLLATE "C", identity COLLATE "C";
+                 type                  |           schema            | name |                    identity                     
+---------------------------------------+-----------------------------+------+-------------------------------------------------
+ property graph element                |                             |      | e of create_property_graph_tests.gt
+ property graph element                |                             |      | v1 of create_property_graph_tests.gt
+ property graph element                |                             |      | v2 of create_property_graph_tests.gt
+ property graph element label          |                             |      | e of e of create_property_graph_tests.gt
+ property graph element label          |                             |      | v1 of v1 of create_property_graph_tests.gt
+ property graph element label          |                             |      | v2 of v2 of create_property_graph_tests.gt
+ property graph element label property |                             |      | a of v1 of v1 of create_property_graph_tests.gt
+ property graph element label property |                             |      | b of v1 of v1 of create_property_graph_tests.gt
+ property graph element label property |                             |      | c of e of e of create_property_graph_tests.gt
+ property graph element label property |                             |      | k1 of e of e of create_property_graph_tests.gt
+ property graph element label property |                             |      | k2 of e of e of create_property_graph_tests.gt
+ property graph element label property |                             |      | m of v2 of v2 of create_property_graph_tests.gt
+ property graph element label property |                             |      | n of v2 of v2 of create_property_graph_tests.gt
+ property graph label                  |                             |      | e of create_property_graph_tests.gt
+ property graph label                  |                             |      | v1 of create_property_graph_tests.gt
+ property graph label                  |                             |      | v2 of create_property_graph_tests.gt
+ property graph property               |                             |      | a of create_property_graph_tests.gt
+ property graph property               |                             |      | b of create_property_graph_tests.gt
+ property graph property               |                             |      | c of create_property_graph_tests.gt
+ property graph property               |                             |      | k1 of create_property_graph_tests.gt
+ property graph property               |                             |      | k2 of create_property_graph_tests.gt
+ property graph property               |                             |      | m of create_property_graph_tests.gt
+ property graph property               |                             |      | n of create_property_graph_tests.gt
+ type                                  | create_property_graph_tests | _gt  | create_property_graph_tests.gt[]
+ type                                  | create_property_graph_tests | gt   | create_property_graph_tests.gt
+(25 rows)
 
 \a\t
 SELECT pg_get_propgraphdef('g2'::regclass);
diff --git a/src/test/regress/expected/event_trigger.out b/src/test/regress/expected/event_trigger.out
index 065f586310f..86ae50ce531 100644
--- a/src/test/regress/expected/event_trigger.out
+++ b/src/test/regress/expected/event_trigger.out
@@ -173,6 +173,28 @@ NOTICE:  test_event_trigger: ddl_command_end CREATE USER MAPPING
 alter default privileges for role regress_evt_user
  revoke delete on tables from regress_evt_user;
 NOTICE:  test_event_trigger: ddl_command_end ALTER DEFAULT PRIVILEGES
+-- DROP PROPERTY GRAPH should work with event trigger in place
+CREATE TABLE tv1 (a int PRIMARY KEY, b text);
+NOTICE:  test_event_trigger: ddl_command_start CREATE TABLE
+NOTICE:  test_event_trigger: ddl_command_end CREATE TABLE
+CREATE TABLE tv2 (i int PRIMARY KEY, j text);
+NOTICE:  test_event_trigger: ddl_command_start CREATE TABLE
+NOTICE:  test_event_trigger: ddl_command_end CREATE TABLE
+CREATE TABLE te1 (p int PRIMARY KEY, a int REFERENCES tv1(a), b int REFERENCES tv2(i), q text);
+NOTICE:  test_event_trigger: ddl_command_start CREATE TABLE
+NOTICE:  test_event_trigger: ddl_command_end CREATE TABLE
+CREATE PROPERTY GRAPH gx
+	VERTEX TABLES (
+		tv1 LABEL l1 PROPERTIES (b AS p1),
+		tv2 LABEL l2 PROPERTIES (j AS p1))
+  EDGE TABLES (te1 SOURCE tv1 DESTINATION tv2 LABEL e1 PROPERTIES (q as p1));
+NOTICE:  test_event_trigger: ddl_command_end CREATE PROPERTY GRAPH
+ALTER PROPERTY GRAPH gx ALTER EDGE TABLE te1 ALTER LABEL e1 DROP PROPERTIES (p1);
+NOTICE:  test_event_trigger: ddl_command_end ALTER PROPERTY GRAPH
+DROP PROPERTY GRAPH gx;
+NOTICE:  test_event_trigger: ddl_command_end DROP PROPERTY GRAPH
+DROP TABLE tv1, tv2, te1;
+NOTICE:  test_event_trigger: ddl_command_end DROP TABLE
 -- alter owner to non-superuser should fail
 alter event trigger regress_event_trigger owner to regress_evt_user;
 ERROR:  permission denied to change owner of event trigger "regress_event_trigger"
diff --git a/src/test/regress/expected/object_address.out b/src/test/regress/expected/object_address.out
index 97227d67a54..9f2d08398b0 100644
--- a/src/test/regress/expected/object_address.out
+++ b/src/test/regress/expected/object_address.out
@@ -67,7 +67,8 @@ DECLARE
 BEGIN
     FOR objtype IN VALUES ('toast table'), ('index column'), ('sequence column'),
         ('toast table column'), ('view column'), ('materialized view column'),
-        ('property graph element'), ('property graph label'), ('property graph property')
+        ('property graph element'), ('property graph label'), ('property graph property'),
+        ('property graph element label'), ('property graph element label property')
     LOOP
         BEGIN
             PERFORM pg_get_object_address(objtype, '{one}', '{}');
@@ -86,6 +87,8 @@ WARNING:  error for materialized view column: unsupported object type "materiali
 WARNING:  error for property graph element: unsupported object type "property graph element"
 WARNING:  error for property graph label: unsupported object type "property graph label"
 WARNING:  error for property graph property: unsupported object type "property graph property"
+WARNING:  error for property graph element label: unrecognized object type "property graph element label"
+WARNING:  error for property graph element label property: unrecognized object type "property graph element label property"
 -- miscellaneous other errors
 select * from pg_get_object_address('operator of access method', '{btree,integer_ops,1}', '{int4,bool}');
 ERROR:  operator 1 (int4, bool) of operator family integer_ops for access method btree does not exist
@@ -595,6 +598,8 @@ WITH objects (classid, objid, objsubid) AS (VALUES
     ('pg_propgraph_element'::regclass, 0, 0), -- no property graph element
     ('pg_propgraph_label'::regclass, 0, 0), -- no property graph label
     ('pg_propgraph_property'::regclass, 0, 0), -- no property graph property
+    ('pg_propgraph_element_label'::regclass, 0, 0), -- no property graph element label
+    ('pg_propgraph_label_property'::regclass, 0, 0), -- no property graph label property
     ('pg_publication'::regclass, 0, 0), -- no publication
     ('pg_publication_namespace'::regclass, 0, 0), -- no publication namespace
     ('pg_publication_rel'::regclass, 0, 0), -- no publication relation
@@ -653,6 +658,8 @@ ORDER BY objects.classid, objects.objid, objects.objsubid;
 ("(""parameter ACL"",,,)")|("(""parameter ACL"",,)")|NULL
 ("(""property graph element"",,,)")|("(""property graph element"",,)")|NULL
 ("(""property graph label"",,,)")|("(""property graph label"",,)")|NULL
+("(""property graph element label"",,,)")|("(""property graph element label"",,)")|NULL
 ("(""property graph property"",,,)")|("(""property graph property"",,)")|NULL
+("(""property graph element label property"",,,)")|("(""property graph element label property"",,)")|NULL
 -- restore normal output mode
 \a\t
diff --git a/src/test/regress/sql/create_property_graph.sql b/src/test/regress/sql/create_property_graph.sql
index 241f93df302..de292b3a39c 100644
--- a/src/test/regress/sql/create_property_graph.sql
+++ b/src/test/regress/sql/create_property_graph.sql
@@ -281,22 +281,32 @@ SELECT * FROM information_schema.pg_property_data_types ORDER BY property_graph_
 SELECT * FROM information_schema.pg_property_graph_privileges WHERE grantee LIKE 'regress%' ORDER BY property_graph_name, grantor, grantee, privilege_type;
 
 -- test object address functions
+CREATE TEMPORARY VIEW deps_tree AS
+    WITH RECURSIVE deps (classid, objid, objsubid, refclassid, refobjid, refobjsubid) AS (
+        SELECT classid, objid, objsubid,
+               refclassid, refobjid, refobjsubid
+        FROM pg_depend
+        WHERE refclassid = 'pg_class'::regclass AND
+              refobjid = 'create_property_graph_tests.gt'::regclass AND
+        -- eliminate this view, which is not a real dependency, from the result
+              classid <> 'pg_rewrite'::regclass
+        UNION ALL
+        SELECT d.classid, d.objid, d.objsubid,
+               d.refclassid, d.refobjid, d.refobjsubid
+        FROM pg_depend d
+        JOIN deps dp ON d.refclassid = dp.classid AND d.refobjid = dp.objid AND d.refobjsubid = dp.objsubid
+    ) SELECT DISTINCT * FROM deps;
+
 SELECT pg_describe_object(classid, objid, objsubid) as obj,
-       pg_describe_object(refclassid, refobjid, refobjsubid) as reference_graph
-    FROM pg_depend
-    WHERE refclassid = 'pg_class'::regclass AND
-          refobjid = 'create_property_graph_tests.g2'::regclass
-    ORDER BY 1, 2;
+       pg_describe_object(refclassid, refobjid, refobjsubid) as refobj
+    FROM deps_tree ORDER BY 1, 2;
 SELECT (pg_identify_object_as_address(classid, objid, objsubid)).*
-    FROM pg_depend
-    WHERE refclassid = 'pg_class'::regclass AND
-          refobjid = 'create_property_graph_tests.g2'::regclass
+    FROM (SELECT DISTINCT classid, objid, objsubid FROM deps_tree)
     ORDER BY 1, 2, 3;
-SELECT (pg_identify_object(classid, objid, objsubid)).*
-    FROM pg_depend
-    WHERE refclassid = 'pg_class'::regclass AND
-          refobjid = 'create_property_graph_tests.g2'::regclass
-    ORDER BY 1, 2, 3, 4;
+SELECT *
+    FROM (SELECT (pg_identify_object(classid, objid, objsubid)).*
+          FROM (SELECT DISTINCT classid, objid, objsubid FROM deps_tree)) s
+    ORDER BY type, schema, name COLLATE "C", identity COLLATE "C";
 
 \a\t
 SELECT pg_get_propgraphdef('g2'::regclass);
diff --git a/src/test/regress/sql/event_trigger.sql b/src/test/regress/sql/event_trigger.sql
index 32e9bb58c5e..d0e6ba295fe 100644
--- a/src/test/regress/sql/event_trigger.sql
+++ b/src/test/regress/sql/event_trigger.sql
@@ -143,6 +143,21 @@ create user mapping for regress_evt_user server useless_server;
 alter default privileges for role regress_evt_user
  revoke delete on tables from regress_evt_user;
 
+-- DROP PROPERTY GRAPH should work with event trigger in place
+CREATE TABLE tv1 (a int PRIMARY KEY, b text);
+CREATE TABLE tv2 (i int PRIMARY KEY, j text);
+CREATE TABLE te1 (p int PRIMARY KEY, a int REFERENCES tv1(a), b int REFERENCES tv2(i), q text);
+
+CREATE PROPERTY GRAPH gx
+	VERTEX TABLES (
+		tv1 LABEL l1 PROPERTIES (b AS p1),
+		tv2 LABEL l2 PROPERTIES (j AS p1))
+  EDGE TABLES (te1 SOURCE tv1 DESTINATION tv2 LABEL e1 PROPERTIES (q as p1));
+
+ALTER PROPERTY GRAPH gx ALTER EDGE TABLE te1 ALTER LABEL e1 DROP PROPERTIES (p1);
+DROP PROPERTY GRAPH gx;
+DROP TABLE tv1, tv2, te1;
+
 -- alter owner to non-superuser should fail
 alter event trigger regress_event_trigger owner to regress_evt_user;
 
diff --git a/src/test/regress/sql/object_address.sql b/src/test/regress/sql/object_address.sql
index 1bbe9457c1c..e987bd3542f 100644
--- a/src/test/regress/sql/object_address.sql
+++ b/src/test/regress/sql/object_address.sql
@@ -67,7 +67,8 @@ DECLARE
 BEGIN
     FOR objtype IN VALUES ('toast table'), ('index column'), ('sequence column'),
         ('toast table column'), ('view column'), ('materialized view column'),
-        ('property graph element'), ('property graph label'), ('property graph property')
+        ('property graph element'), ('property graph label'), ('property graph property'),
+        ('property graph element label'), ('property graph element label property')
     LOOP
         BEGIN
             PERFORM pg_get_object_address(objtype, '{one}', '{}');
@@ -284,6 +285,8 @@ WITH objects (classid, objid, objsubid) AS (VALUES
     ('pg_propgraph_element'::regclass, 0, 0), -- no property graph element
     ('pg_propgraph_label'::regclass, 0, 0), -- no property graph label
     ('pg_propgraph_property'::regclass, 0, 0), -- no property graph property
+    ('pg_propgraph_element_label'::regclass, 0, 0), -- no property graph element label
+    ('pg_propgraph_label_property'::regclass, 0, 0), -- no property graph label property
     ('pg_publication'::regclass, 0, 0), -- no publication
     ('pg_publication_namespace'::regclass, 0, 0), -- no publication namespace
     ('pg_publication_rel'::regclass, 0, 0), -- no publication relation
-- 
2.34.1


--ytT+cZ3WhQPHkm7m--





^ permalink  raw  reply  [nested|flat] 58+ messages in thread


end of thread, other threads:[~2026-04-23 09:27 UTC | newest]

Thread overview: 58+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2026-04-20 09:38 [PATCH 1/2] REPACK: do not require the user to have REPLICATION Álvaro Herrera <[email protected]>
2026-04-20 09:38 [PATCH 1/2] REPACK: do not require the user to have REPLICATION Álvaro Herrera <[email protected]>
2026-04-20 09:38 [PATCH 1/2] REPACK: do not require the user to have REPLICATION Álvaro Herrera <[email protected]>
2026-04-20 09:38 [PATCH 1/2] REPACK: do not require the user to have REPLICATION Álvaro Herrera <[email protected]>
2026-04-20 09:38 [PATCH 1/2] REPACK: do not require the user to have REPLICATION Álvaro Herrera <[email protected]>
2026-04-20 09:38 [PATCH 1/2] REPACK: do not require the user to have REPLICATION Álvaro Herrera <[email protected]>
2026-04-20 09:38 [PATCH 1/2] REPACK: do not require the user to have REPLICATION Álvaro Herrera <[email protected]>
2026-04-20 09:38 [PATCH 1/2] REPACK: do not require the user to have REPLICATION Álvaro Herrera <[email protected]>
2026-04-20 09:38 [PATCH 1/2] REPACK: do not require the user to have REPLICATION Álvaro Herrera <[email protected]>
2026-04-20 09:38 [PATCH 1/2] REPACK: do not require the user to have REPLICATION Álvaro Herrera <[email protected]>
2026-04-20 09:38 [PATCH 1/2] REPACK: do not require the user to have REPLICATION Álvaro Herrera <[email protected]>
2026-04-20 09:38 [PATCH 1/2] REPACK: do not require the user to have REPLICATION Álvaro Herrera <[email protected]>
2026-04-20 09:38 [PATCH 1/2] REPACK: do not require the user to have REPLICATION Álvaro Herrera <[email protected]>
2026-04-20 09:38 [PATCH 1/2] REPACK: do not require the user to have REPLICATION Álvaro Herrera <[email protected]>
2026-04-20 09:38 [PATCH 1/2] REPACK: do not require the user to have REPLICATION Álvaro Herrera <[email protected]>
2026-04-20 09:38 [PATCH 1/2] REPACK: do not require the user to have REPLICATION Álvaro Herrera <[email protected]>
2026-04-20 09:38 [PATCH 1/2] REPACK: do not require the user to have REPLICATION Álvaro Herrera <[email protected]>
2026-04-20 09:38 [PATCH 1/2] REPACK: do not require the user to have REPLICATION Álvaro Herrera <[email protected]>
2026-04-20 09:38 [PATCH 1/2] REPACK: do not require the user to have REPLICATION Álvaro Herrera <[email protected]>
2026-04-20 09:38 [PATCH 1/2] REPACK: do not require the user to have REPLICATION Álvaro Herrera <[email protected]>
2026-04-20 09:38 [PATCH 1/2] REPACK: do not require the user to have REPLICATION Álvaro Herrera <[email protected]>
2026-04-20 09:38 [PATCH 1/2] REPACK: do not require the user to have REPLICATION Álvaro Herrera <[email protected]>
2026-04-20 09:38 [PATCH 1/2] REPACK: do not require the user to have REPLICATION Álvaro Herrera <[email protected]>
2026-04-20 09:38 [PATCH 1/2] REPACK: do not require the user to have REPLICATION Álvaro Herrera <[email protected]>
2026-04-20 09:38 [PATCH 1/2] REPACK: do not require the user to have REPLICATION Álvaro Herrera <[email protected]>
2026-04-20 09:38 [PATCH 1/2] REPACK: do not require the user to have REPLICATION Álvaro Herrera <[email protected]>
2026-04-20 09:38 [PATCH 1/2] REPACK: do not require the user to have REPLICATION Álvaro Herrera <[email protected]>
2026-04-20 09:38 [PATCH 1/2] REPACK: do not require the user to have REPLICATION Álvaro Herrera <[email protected]>
2026-04-20 09:38 [PATCH 1/2] REPACK: do not require the user to have REPLICATION Álvaro Herrera <[email protected]>
2026-04-20 09:38 [PATCH 1/2] REPACK: do not require the user to have REPLICATION Álvaro Herrera <[email protected]>
2026-04-20 09:38 [PATCH 1/2] REPACK: do not require the user to have REPLICATION Álvaro Herrera <[email protected]>
2026-04-20 09:38 [PATCH 1/2] REPACK: do not require the user to have REPLICATION Álvaro Herrera <[email protected]>
2026-04-20 09:38 [PATCH 1/2] REPACK: do not require the user to have REPLICATION Álvaro Herrera <[email protected]>
2026-04-20 09:38 [PATCH 1/2] REPACK: do not require the user to have REPLICATION Álvaro Herrera <[email protected]>
2026-04-20 09:38 [PATCH 1/2] REPACK: do not require the user to have REPLICATION Álvaro Herrera <[email protected]>
2026-04-20 09:38 [PATCH 1/2] REPACK: do not require the user to have REPLICATION Álvaro Herrera <[email protected]>
2026-04-20 09:38 [PATCH 1/2] REPACK: do not require the user to have REPLICATION Álvaro Herrera <[email protected]>
2026-04-20 09:38 [PATCH 1/2] REPACK: do not require the user to have REPLICATION Álvaro Herrera <[email protected]>
2026-04-20 09:38 [PATCH 1/2] REPACK: do not require the user to have REPLICATION Álvaro Herrera <[email protected]>
2026-04-20 09:38 [PATCH 1/2] REPACK: do not require the user to have REPLICATION Álvaro Herrera <[email protected]>
2026-04-20 09:38 [PATCH 1/2] REPACK: do not require the user to have REPLICATION Álvaro Herrera <[email protected]>
2026-04-20 09:38 [PATCH 1/2] REPACK: do not require the user to have REPLICATION Álvaro Herrera <[email protected]>
2026-04-20 09:38 [PATCH 1/2] REPACK: do not require the user to have REPLICATION Álvaro Herrera <[email protected]>
2026-04-20 09:38 [PATCH 1/2] REPACK: do not require the user to have REPLICATION Álvaro Herrera <[email protected]>
2026-04-20 09:38 [PATCH 1/2] REPACK: do not require the user to have REPLICATION Álvaro Herrera <[email protected]>
2026-04-20 09:38 [PATCH 1/2] REPACK: do not require the user to have REPLICATION Álvaro Herrera <[email protected]>
2026-04-20 09:38 [PATCH 1/2] REPACK: do not require the user to have REPLICATION Álvaro Herrera <[email protected]>
2026-04-20 09:38 [PATCH 1/2] REPACK: do not require the user to have REPLICATION Álvaro Herrera <[email protected]>
2026-04-20 09:38 [PATCH 1/2] REPACK: do not require the user to have REPLICATION Álvaro Herrera <[email protected]>
2026-04-20 09:38 [PATCH 1/2] REPACK: do not require the user to have REPLICATION Álvaro Herrera <[email protected]>
2026-04-20 09:38 [PATCH 1/2] REPACK: do not require the user to have REPLICATION Álvaro Herrera <[email protected]>
2026-04-20 09:38 [PATCH 1/2] REPACK: do not require the user to have REPLICATION Álvaro Herrera <[email protected]>
2026-04-20 09:38 [PATCH 1/2] REPACK: do not require the user to have REPLICATION Álvaro Herrera <[email protected]>
2026-04-20 09:38 [PATCH 1/2] REPACK: do not require the user to have REPLICATION Álvaro Herrera <[email protected]>
2026-04-20 09:38 [PATCH 1/2] REPACK: do not require the user to have REPLICATION Álvaro Herrera <[email protected]>
2026-04-20 09:38 [PATCH 1/2] REPACK: do not require the user to have REPLICATION Álvaro Herrera <[email protected]>
2026-04-20 09:38 [PATCH 1/2] REPACK: do not require the user to have REPLICATION Álvaro Herrera <[email protected]>
2026-04-23 09:27 [PATCH v5] Handle element label and element label property objects in object address functions Bertrand Drouvot <[email protected]>

This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox