On 17/10/2026 13:01, Jim Jones wrote:
> Ok, I will create a followup thread only with the contrib fixes and take
> this into account for pageinspect. Thanks.

I created another CF entry dedicated to the contrib modules:

https://commitfest.postgresql.org/patch/7478/

I also added a small test into 001_stream_rep.pl to check the currti2 fix.

Thanks!

Best, Jim
From e33addb960dac792886d53adab6eafe5ff62422e Mon Sep 17 00:00:00 2001
From: Jim Jones <[email protected]>
Date: Fri, 16 Oct 2026 09:12:23 +0200
Subject: [PATCH v4 1/2] Reject COPY TO of unlogged tables during recovery

Unlogged tables have no data on a standby, so reading one there must
fail.  SELECT and COPY (SELECT ...) TO already report a clear error,
but the table form of COPY TO reads the table directly and fails with
a confusing low-level message instead:

  ERROR:  could not open file "base/5/16384": No such file or directory

Check for this case in COPY TO and report a proper error.  When copying
from a partitioned table, also check each partition, so that an
unlogged partition is also reported.

Author: Jim Jones <[email protected]>
Reviewed-by: Zsolt Parragi <[email protected]>
Discussion: https://www.postgresql.org/message-id/flat/b6d71815-d362-4579-bf07-73eca01aee6d%40uni-muenster.de
---
 src/backend/commands/copyto.c         | 19 +++++++++++++++++++
 src/test/recovery/t/001_stream_rep.pl | 27 +++++++++++++++++++++++++++
 2 files changed, 46 insertions(+)

diff --git a/src/backend/commands/copyto.c b/src/backend/commands/copyto.c
index a187d8ec284..9ca06ed3e18 100644
--- a/src/backend/commands/copyto.c
+++ b/src/backend/commands/copyto.c
@@ -21,6 +21,7 @@
 #include "access/table.h"
 #include "access/tableam.h"
 #include "access/tupconvert.h"
+#include "access/xlog.h"
 #include "catalog/pg_inherits.h"
 #include "commands/copyapi.h"
 #include "commands/progress.h"
@@ -877,6 +878,19 @@ BeginCopyTo(ParseState *pstate,
 							errhint("Try the COPY (SELECT ...) TO variant."));
 				}
 
+				/* Unlogged partitions are inaccessible during recovery. */
+				if (RecoveryInProgress() &&
+					get_rel_persistence(child) == RELPERSISTENCE_UNLOGGED)
+				{
+					char	   *relation_name = get_rel_name(child);
+
+					ereport(ERROR,
+							errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							errmsg("cannot access temporary or unlogged relations during recovery"),
+							errdetail("Partition \"%s\" is an unlogged table in partitioned table \"%s\".",
+									  relation_name, RelationGetRelationName(rel)));
+				}
+
 				/* Exclude tables with no data */
 				if (RELKIND_HAS_PARTITIONS(relkind))
 					children = foreach_delete_current(children, child);
@@ -889,6 +903,11 @@ BeginCopyTo(ParseState *pstate,
 							RelationGetRelationName(rel))));
 	}
 
+	/* Temporary and unlogged relations are inaccessible during recovery. */
+	if (rel != NULL && !RelationIsPermanent(rel) && RecoveryInProgress())
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("cannot access temporary or unlogged relations during recovery")));
 
 	/* Allocate workspace and zero all fields */
 	cstate = palloc0_object(CopyToStateData);
diff --git a/src/test/recovery/t/001_stream_rep.pl b/src/test/recovery/t/001_stream_rep.pl
index f05580a8d6d..4a3c7d71d39 100644
--- a/src/test/recovery/t/001_stream_rep.pl
+++ b/src/test/recovery/t/001_stream_rep.pl
@@ -113,6 +113,33 @@ is( $node_standby_1->safe_psql(
 	't',
 	'pg_sequence_last_value() on unlogged sequence on standby 1');
 
+# Check that COPY TO rejects unlogged tables on standby, including when
+# reached through a partitioned table, but still copies permanent ones
+$node_primary->safe_psql(
+	'postgres', q{
+CREATE UNLOGGED TABLE ultab (a int);
+CREATE TABLE ulpart (a int) PARTITION BY RANGE (a);
+CREATE UNLOGGED TABLE ulpart_1 PARTITION OF ulpart FOR VALUES FROM (0) TO (10);
+CREATE TABLE lpart (a int) PARTITION BY RANGE (a);
+CREATE TABLE lpart_1 PARTITION OF lpart FOR VALUES FROM (0) TO (10);
+INSERT INTO lpart VALUES (1);
+});
+$node_primary->wait_for_replay_catchup($node_standby_1);
+my ($copy_ret, $copy_stdout, $copy_stderr) =
+  $node_standby_1->psql('postgres', 'COPY ultab TO STDOUT');
+like(
+	$copy_stderr,
+	qr/cannot access temporary or unlogged relations during recovery/,
+	'COPY TO of unlogged table on standby 1');
+($copy_ret, $copy_stdout, $copy_stderr) =
+  $node_standby_1->psql('postgres', 'COPY ulpart TO STDOUT');
+like(
+	$copy_stderr,
+	qr/cannot access temporary or unlogged relations during recovery.*\n.*DETAIL:  Partition "ulpart_1" is an unlogged table in partitioned table "ulpart"/,
+	'COPY TO of partitioned table with unlogged partition on standby 1');
+is($node_standby_1->safe_psql('postgres', 'COPY lpart TO STDOUT'),
+	'1', 'COPY TO of partitioned table on standby 1');
+
 # Check that only READ-only queries can run on standbys
 is($node_standby_1->psql('postgres', 'INSERT INTO tab_int VALUES (1)'),
 	3, 'read-only queries on standby 1');
-- 
2.56.0

From cc3f122497418703b635f484081c15f857f7308a Mon Sep 17 00:00:00 2001
From: Jim Jones <[email protected]>
Date: Sun, 18 Oct 2026 14:58:39 +0200
Subject: [PATCH v4 2/2] Reject currtid2() on unlogged tables during recovery

Unlogged tables have no data on a standby, so reading one there must
fail.  currtid2() reads the table directly instead of going through the
planner, so on a standby it failed with a confusing low-level message
instead of the error that SELECT reports, both for a table and for a
view on top of one:

  ERROR:  could not open file "base/5/16384": No such file or directory

Check for this case and report the same error as SELECT:

  ERROR:  cannot access temporary or unlogged relations during recovery

Author: Jim Jones <[email protected]>
Reported-by: Masahiko Sawada <[email protected]>
Discussion: https://www.postgresql.org/message-id/b6d71815-d362-4579-bf07-73eca01aee6d%40uni-muenster.de
---
 src/backend/utils/adt/tid.c           |  7 +++++++
 src/test/recovery/t/001_stream_rep.pl | 23 +++++++++++++++++++++++
 2 files changed, 30 insertions(+)

diff --git a/src/backend/utils/adt/tid.c b/src/backend/utils/adt/tid.c
index a97873f91ba..f730b2f07c2 100644
--- a/src/backend/utils/adt/tid.c
+++ b/src/backend/utils/adt/tid.c
@@ -22,6 +22,7 @@
 #include "access/sysattr.h"
 #include "access/table.h"
 #include "access/tableam.h"
+#include "access/xlog.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_type.h"
 #include "common/hashfn.h"
@@ -345,6 +346,12 @@ currtid_internal(Relation rel, const ItemPointerData *tid)
 					   get_namespace_name(RelationGetNamespace(rel)),
 					   RelationGetRelationName(rel)));
 
+	/* Temporary and unlogged relations are inaccessible during recovery. */
+	if (!RelationIsPermanent(rel) && RecoveryInProgress())
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("cannot access temporary or unlogged relations during recovery")));
+
 	ItemPointerCopy(tid, result);
 
 	snapshot = RegisterSnapshot(GetLatestSnapshot());
diff --git a/src/test/recovery/t/001_stream_rep.pl b/src/test/recovery/t/001_stream_rep.pl
index 4a3c7d71d39..e481ed8b798 100644
--- a/src/test/recovery/t/001_stream_rep.pl
+++ b/src/test/recovery/t/001_stream_rep.pl
@@ -140,6 +140,29 @@ like(
 is($node_standby_1->safe_psql('postgres', 'COPY lpart TO STDOUT'),
 	'1', 'COPY TO of partitioned table on standby 1');
 
+# Check that currtid2() rejects unlogged tables on standby, including when
+# reached through a view, but still works on permanent ones
+$node_primary->safe_psql('postgres',
+	'CREATE VIEW ultab_view AS SELECT ctid, a FROM ultab');
+$node_primary->wait_for_replay_catchup($node_standby_1);
+my ($tid_ret, $tid_stdout, $tid_stderr) =
+  $node_standby_1->psql('postgres', q{SELECT currtid2('ultab', '(0,1)')});
+like(
+	$tid_stderr,
+	qr/cannot access temporary or unlogged relations during recovery/,
+	'currtid2() on unlogged table on standby 1');
+($tid_ret, $tid_stdout, $tid_stderr) =
+  $node_standby_1->psql('postgres',
+	q{SELECT currtid2('ultab_view', '(0,1)')});
+like(
+	$tid_stderr,
+	qr/cannot access temporary or unlogged relations during recovery/,
+	'currtid2() on view over unlogged table on standby 1');
+is( $node_standby_1->safe_psql(
+		'postgres', q{SELECT currtid2('tab_int', '(0,1)')}),
+	'(0,1)',
+	'currtid2() on permanent table on standby 1');
+
 # Check that only READ-only queries can run on standbys
 is($node_standby_1->psql('postgres', 'INSERT INTO tab_int VALUES (1)'),
 	3, 'read-only queries on standby 1');
-- 
2.56.0

Reply via email to