Hi Kirill

On 25/10/2026 07:11, Kirill Reshke wrote:
> Then on standby:
> 
> reshke=# table u_toast;
> ERROR:  cannot access temporary or unlogged relations during recovery
> reshke=#     SELECT octet_length(t_attrs[1])
>       FROM u_snapshot,
>            LATERAL heap_page_item_attrs(raw, 'u_toast'::regclass, true);
> ERROR:  could not open file "base/16384/16393": No such file or directory
> CONTEXT:  SQL function "heap_page_item_attrs" statement 1

Nice catch! I could reproduce the error and can confirm that your patch
fixes it -- now merged into 0001. The remaining patches 0002 - 0004
remain unchanged.
> 
> attached fixes an issue.
> Also, Im +0.5 on not creating a TAP test for that -- too many cpu
> cycles for too little benefit.

Yeah, this is was my rationale as well.

v2 attached.

Thanks!

Best, Jim
From 5ac944c873b6637c04e36f1ba1a5edcb8b347d1c Mon Sep 17 00:00:00 2001
From: Jim Jones <[email protected]>
Date: Sun, 25 Oct 2026 11:06:06 +0100
Subject: [PATCH v2 1/4] pageinspect: Reject unlogged relations during recovery

Unlogged tables and indexes have no data on a standby, so reading one
there must fail.  get_raw_page(), bt_metap(), bt_page_stats(),
bt_multi_page_stats(), bt_page_items() and hash_bitmap_info() read the
relation directly instead of going through the planner, so on a standby
they failed with a confusing low-level message instead of the error
that SELECT reports:

  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

The init fork of an unlogged relation does exist on a standby, so
get_raw_page() can still read it.

Author: Jim Jones <[email protected]>
Co-Authored-by: Kirill Reshke <[email protected]>
Reported-by: Zsolt Parragi <[email protected]>
---
 contrib/pageinspect/btreefuncs.c | 13 +++++++++++++
 contrib/pageinspect/hashfuncs.c  |  7 +++++++
 contrib/pageinspect/heapfuncs.c  | 10 ++++++++++
 contrib/pageinspect/rawpage.c    | 11 +++++++++++
 4 files changed, 41 insertions(+)

diff --git a/contrib/pageinspect/btreefuncs.c b/contrib/pageinspect/btreefuncs.c
index 9917663593b..676afe51de8 100644
--- a/contrib/pageinspect/btreefuncs.c
+++ b/contrib/pageinspect/btreefuncs.c
@@ -30,6 +30,7 @@
 #include "access/htup_details.h"
 #include "access/nbtree.h"
 #include "access/relation.h"
+#include "access/xlog.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_type.h"
@@ -241,6 +242,12 @@ bt_index_block_validate(Relation rel, int64 blkno)
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("cannot access temporary tables of other sessions")));
 
+	/* 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")));
+
 	if (blkno == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -874,6 +881,12 @@ bt_metap(PG_FUNCTION_ARGS)
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("cannot access temporary tables of other sessions")));
 
+	/* 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")));
+
 	buffer = ReadBuffer(rel, 0);
 	LockBuffer(buffer, BUFFER_LOCK_SHARE);
 
diff --git a/contrib/pageinspect/hashfuncs.c b/contrib/pageinspect/hashfuncs.c
index 30870329cc9..0f728d9add8 100644
--- a/contrib/pageinspect/hashfuncs.c
+++ b/contrib/pageinspect/hashfuncs.c
@@ -13,6 +13,7 @@
 #include "access/hash.h"
 #include "access/htup_details.h"
 #include "access/relation.h"
+#include "access/xlog.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_type.h"
 #include "funcapi.h"
@@ -431,6 +432,12 @@ hash_bitmap_info(PG_FUNCTION_ARGS)
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("cannot access temporary tables of other sessions")));
 
+	/* Temporary and unlogged relations are inaccessible during recovery. */
+	if (!RelationIsPermanent(indexRel) && RecoveryInProgress())
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("cannot access temporary or unlogged relations during recovery")));
+
 	if (ovflblkno < 0 || ovflblkno > MaxBlockNumber)
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
diff --git a/contrib/pageinspect/heapfuncs.c b/contrib/pageinspect/heapfuncs.c
index 7be4770dc84..57068eca2dd 100644
--- a/contrib/pageinspect/heapfuncs.c
+++ b/contrib/pageinspect/heapfuncs.c
@@ -27,6 +27,7 @@
 
 #include "access/htup_details.h"
 #include "access/relation.h"
+#include "access/xlog.h"
 #include "catalog/pg_am_d.h"
 #include "catalog/pg_type.h"
 #include "funcapi.h"
@@ -389,7 +390,16 @@ tuple_data_split_internal(Oid relid, char *tupdata,
 						 errmsg("unexpected end of tuple data")));
 
 			if (attr->attlen == -1 && do_detoast)
+			{
+				/* A saved page can still refer to unlogged TOAST storage. */
+				if (VARATT_IS_EXTERNAL_ONDISK(tupdata + off) &&
+					!RelationIsPermanent(rel) && RecoveryInProgress())
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("cannot access temporary or unlogged relations during recovery")));
+
 				attr_data = pg_detoast_datum_copy((varlena *) (tupdata + off));
+			}
 			else
 			{
 				attr_data = (bytea *) palloc(len + VARHDRSZ);
diff --git a/contrib/pageinspect/rawpage.c b/contrib/pageinspect/rawpage.c
index d136593edb2..05ecde3c20f 100644
--- a/contrib/pageinspect/rawpage.c
+++ b/contrib/pageinspect/rawpage.c
@@ -17,6 +17,7 @@
 
 #include "access/htup_details.h"
 #include "access/relation.h"
+#include "access/xlog.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_type.h"
 #include "funcapi.h"
@@ -175,6 +176,16 @@ get_raw_page_internal(text *relname, ForkNumber forknum, BlockNumber blkno)
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("cannot access temporary tables of other sessions")));
 
+	/*
+	 * Temporary and unlogged relations are inaccessible during recovery,
+	 * except for the init fork of an unlogged relation, which does exist.
+	 */
+	if (!RelationIsPermanent(rel) && RecoveryInProgress() &&
+		forknum != INIT_FORKNUM)
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("cannot access temporary or unlogged relations during recovery")));
+
 	if (blkno >= RelationGetNumberOfBlocksInFork(rel, forknum))
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
-- 
2.56.0

From 8bdc4a97aca4b1b9c8d8ce46777b8b5d4c8bee61 Mon Sep 17 00:00:00 2001
From: Jim Jones <[email protected]>
Date: Sun, 18 Oct 2026 14:07:37 +0200
Subject: [PATCH v2 2/4] pgrowlocks: Reject unlogged tables during recovery

Unlogged tables have no data on a standby, so reading one there must
fail.  pgrowlocks() 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:

  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: Zsolt Parragi <[email protected]>
---
 contrib/pgrowlocks/pgrowlocks.c | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/contrib/pgrowlocks/pgrowlocks.c b/contrib/pgrowlocks/pgrowlocks.c
index 87bbde59937..84017271e98 100644
--- a/contrib/pgrowlocks/pgrowlocks.c
+++ b/contrib/pgrowlocks/pgrowlocks.c
@@ -29,6 +29,7 @@
 #include "access/relscan.h"
 #include "access/tableam.h"
 #include "access/xact.h"
+#include "access/xlog.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_am_d.h"
 #include "catalog/pg_authid.h"
@@ -114,6 +115,12 @@ pgrowlocks(PG_FUNCTION_ARGS)
 		aclcheck_error(aclresult, get_relkind_objtype(rel->rd_rel->relkind),
 					   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")));
+
 	/* Scan the relation */
 	scan = table_beginscan(rel, GetActiveSnapshot(), 0, NULL, SO_NONE);
 	hscan = (HeapScanDesc) scan;
-- 
2.56.0

From 51fdf3cf462e5a2e11b6b07aac69bc9723681fbc Mon Sep 17 00:00:00 2001
From: Jim Jones <[email protected]>
Date: Sun, 18 Oct 2026 14:09:33 +0200
Subject: [PATCH v2 3/4] pgstattuple: Reject unlogged relations during recovery

Unlogged tables and indexes have no data on a standby, so reading one
there must fail.  pgstattuple(), pgstattuple_approx(), pg_relpages(),
pgstatindex(), pgstatginindex() and pgstathashindex() read the relation
directly instead of going through the planner, so on a standby they
failed with a confusing low-level message instead of the error that
SELECT reports:

  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: Zsolt Parragi <[email protected]>
---
 contrib/pgstattuple/pgstatapprox.c |  7 +++++++
 contrib/pgstattuple/pgstatindex.c  | 25 +++++++++++++++++++++++++
 contrib/pgstattuple/pgstattuple.c  |  7 +++++++
 3 files changed, 39 insertions(+)

diff --git a/contrib/pgstattuple/pgstatapprox.c b/contrib/pgstattuple/pgstatapprox.c
index 21e0b50fb4b..38b0cc951e2 100644
--- a/contrib/pgstattuple/pgstatapprox.c
+++ b/contrib/pgstattuple/pgstatapprox.c
@@ -16,6 +16,7 @@
 #include "access/htup_details.h"
 #include "access/relation.h"
 #include "access/visibilitymap.h"
+#include "access/xlog.h"
 #include "catalog/pg_am_d.h"
 #include "commands/vacuum.h"
 #include "funcapi.h"
@@ -332,6 +333,12 @@ pgstattuple_approx_internal(Oid relid, FunctionCallInfo fcinfo)
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("cannot access temporary tables of other sessions")));
 
+	/* 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")));
+
 	/*
 	 * We support only relation kinds with a visibility map and a free space
 	 * map.
diff --git a/contrib/pgstattuple/pgstatindex.c b/contrib/pgstattuple/pgstatindex.c
index 8951ad0aac4..87e79527361 100644
--- a/contrib/pgstattuple/pgstatindex.c
+++ b/contrib/pgstattuple/pgstatindex.c
@@ -32,6 +32,7 @@
 #include "access/htup_details.h"
 #include "access/nbtree.h"
 #include "access/relation.h"
+#include "access/xlog.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_am.h"
 #include "funcapi.h"
@@ -238,6 +239,12 @@ pgstatindex_impl(Relation rel, FunctionCallInfo fcinfo)
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("cannot access temporary tables of other sessions")));
 
+	/* 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")));
+
 	/*
 	 * A !indisready index could lead to ERRCODE_DATA_CORRUPTED later, so exit
 	 * early.  We're capable of assessing an indisready&&!indisvalid index,
@@ -482,6 +489,12 @@ pg_relpages_impl(Relation rel)
 						RelationGetRelationName(rel)),
 				 errdetail_relkind_not_supported(rel->rd_rel->relkind)));
 
+	/* 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")));
+
 	/* note: this will work OK on non-local temp tables */
 
 	relpages = RelationGetNumberOfBlocks(rel);
@@ -557,6 +570,12 @@ pgstatginindex_internal(Oid relid, FunctionCallInfo fcinfo)
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("cannot access temporary indexes of other sessions")));
 
+	/* 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")));
+
 	/* see pgstatindex_impl */
 	if (!rel->rd_index->indisvalid)
 		ereport(ERROR,
@@ -647,6 +666,12 @@ pgstathashindex(PG_FUNCTION_ARGS)
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("cannot access temporary indexes of other sessions")));
 
+	/* 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")));
+
 	/* see pgstatindex_impl */
 	if (!rel->rd_index->indisvalid)
 		ereport(ERROR,
diff --git a/contrib/pgstattuple/pgstattuple.c b/contrib/pgstattuple/pgstattuple.c
index 6a7f8cb4a7c..f9495390673 100644
--- a/contrib/pgstattuple/pgstattuple.c
+++ b/contrib/pgstattuple/pgstattuple.c
@@ -30,6 +30,7 @@
 #include "access/nbtree.h"
 #include "access/relscan.h"
 #include "access/tableam.h"
+#include "access/xlog.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_am_d.h"
 #include "funcapi.h"
@@ -254,6 +255,12 @@ pgstat_relation(Relation rel, FunctionCallInfo fcinfo)
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("cannot access temporary tables of other sessions")));
 
+	/* 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")));
+
 	if (RELKIND_HAS_TABLE_AM(rel->rd_rel->relkind) ||
 		rel->rd_rel->relkind == RELKIND_SEQUENCE)
 	{
-- 
2.56.0

From 9cad4e80df9658cfdd5ae62d9f630b2357ace611 Mon Sep 17 00:00:00 2001
From: Jim Jones <[email protected]>
Date: Sun, 18 Oct 2026 14:10:58 +0200
Subject: [PATCH v2 4/4] pg_visibility: Reject unlogged tables during recovery

Unlogged tables have no data on a standby, so reading one there must
fail.  pg_visibility(), pg_visibility_map(regclass), pg_check_frozen()
and pg_check_visible() read the table directly instead of going through
the planner, so on a standby they failed with a confusing low-level
message instead of the error that SELECT reports:

  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

pg_visibility_map(regclass, bigint) and pg_visibility_map_summary()
already return a result on a standby, so they are left unchanged.

Author: Jim Jones <[email protected]>
Reported-by: Zsolt Parragi <[email protected]>
---
 contrib/pg_visibility/pg_visibility.c | 19 +++++++++++++++++++
 1 file changed, 19 insertions(+)

diff --git a/contrib/pg_visibility/pg_visibility.c b/contrib/pg_visibility/pg_visibility.c
index 4292d077be1..054503319c6 100644
--- a/contrib/pg_visibility/pg_visibility.c
+++ b/contrib/pg_visibility/pg_visibility.c
@@ -13,6 +13,7 @@
 #include "access/heapam.h"
 #include "access/htup_details.h"
 #include "access/visibilitymap.h"
+#include "access/xlog.h"
 #include "access/xloginsert.h"
 #include "catalog/pg_type.h"
 #include "catalog/storage_xlog.h"
@@ -138,6 +139,12 @@ pg_visibility(PG_FUNCTION_ARGS)
 	/* Only some relkinds have a visibility map */
 	check_relation_relkind(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")));
+
 	if (blkno < 0 || blkno > MaxBlockNumber)
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -497,6 +504,12 @@ collect_visibility_data(Oid relid, bool include_pd)
 	/* Only some relkinds have a visibility map */
 	check_relation_relkind(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")));
+
 	nblocks = RelationGetNumberOfBlocks(rel);
 	info = palloc0(offsetof(vbits, bits) + nblocks);
 	info->next = 0;
@@ -711,6 +724,12 @@ collect_corrupt_items(Oid relid, bool all_visible, bool all_frozen)
 	/* Only some relkinds have a visibility map */
 	check_relation_relkind(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")));
+
 	if (all_visible)
 		OldestXmin = GetStrictOldestNonRemovableTransactionId(rel);
 
-- 
2.56.0

Reply via email to