On 02/11/2026 08:38, Michael Paquier wrote:
> I have a question here. Is there a reason why you do not check after
> pg_visibility_map() and pg_visibility_map_summary()? pgstatapprox.c
> also does lookups based on block numbers and you are proposing to
> block temp and unlogged relations there. For the visibility maps, I
> doubt that it makes sense to have them return incorrect results during
> recovery. Here this means vm_readbuf() returning an invalid buffer.
> That feels confusing to me vs the choice of an error.
The only reason is that they didn't fail, so I didn't look any further..
postgres=# select * from pg_visibility_map_summary('pg_temp_1.tmp');
all_visible | all_frozen
-------------+------------
0 | 0
(1 row)
postgres=# SELECT * FROM pg_visibility_map_summary('ut');
all_visible | all_frozen
-------------+------------
0 | 0
(1 row)
...but I agree that an error message is better than incorrect results.
> Another point may be in introducing RELATION_IS_OTHER_TEMP() in
> pg_visibility. It also seems strange to not fail in this case,
> doesn't it? We would just grab incorrect data if trying to fetch VM
> information from a temp relation owned by a different session.
Agreed. I added this check in 0005.
> Rather than copy-pasting the same check N times, you could just group
> them in a shared routine and reduce the duplication. For
> pg_visibility, this could be embedded in check_relation_relkind(),
> perhaps..
Right. I just repeated an existing pattern, but I agree that it can be
done in a single place. It is indeed much better than repeating the
check multiple times. 0004 now adds it in check_relation_relkind().
Thanks!
Best, JimFrom 95961248295727a60623aaee272b8fec6f52be5c Mon Sep 17 00:00:00 2001
From: Jim Jones <[email protected]>
Date: Sun, 25 Oct 2026 11:06:06 +0100
Subject: [PATCH v3 1/5] 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.
heap_page_item_attrs() and tuple_data_split() can also reach unlogged
storage when detoasting values from a saved page image, so check for
this case before detoasting as well.
Author: Jim Jones <[email protected]>
Co-authored-by: Kirill Reshke <[email protected]>
Reported-by: Zsolt Parragi <[email protected]>
Reviewed-by: Michael Paquier <[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 4ed15acad3884af3bf5d5c4d91e2445c1317b587 Mon Sep 17 00:00:00 2001
From: Jim Jones <[email protected]>
Date: Sun, 18 Oct 2026 14:07:37 +0200
Subject: [PATCH v3 2/5] 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]>
Reviewed-by: Kirill Reshke <[email protected]>
Reviewed-by: Michael Paquier <[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 7f346ea46fcca7811a46ab29a9d6739231a9144d Mon Sep 17 00:00:00 2001
From: Jim Jones <[email protected]>
Date: Sun, 18 Oct 2026 14:09:33 +0200
Subject: [PATCH v3 3/5] 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]>
Reviewed-by: Kirill Reshke <[email protected]>
Reviewed-by: Michael Paquier <[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 65a894a6516bf870e157f0e86fc58517bbf03e81 Mon Sep 17 00:00:00 2001
From: Jim Jones <[email protected]>
Date: Mon, 2 Nov 2026 10:03:26 +0100
Subject: [PATCH v3 4/5] pg_visibility: Reject unlogged tables during recovery
Unlogged tables have no data on a standby, so reading one there must
fail. The pg_visibility functions read the table or its visibility map
directly instead of going through the planner. On a standby, some of
them 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
while pg_visibility_map(regclass, bigint) and pg_visibility_map_summary()
reported every page as neither all-visible nor all-frozen, because the
visibility map fork does not exist there.
Check for this case in check_relation_relkind(), which every function
calls, 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]>
Reviewed-by: Kirill Reshke <[email protected]>
Reviewed-by: Michael Paquier <[email protected]>
---
contrib/pg_visibility/pg_visibility.c | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)
diff --git a/contrib/pg_visibility/pg_visibility.c b/contrib/pg_visibility/pg_visibility.c
index 717c851583f..4195aeacc2a 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"
@@ -918,7 +919,7 @@ tuple_all_visible(HeapTuple tup, TransactionId OldestXmin, Buffer buffer)
/*
* check_relation_relkind - convenience routine to check that relation
- * is of the relkind supported by the callers
+ * is of the relkind supported by the callers and can be accessed
*/
static void
check_relation_relkind(Relation rel)
@@ -929,4 +930,10 @@ check_relation_relkind(Relation rel)
errmsg("relation \"%s\" is of wrong relation kind",
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")));
}
--
2.56.0
From f2b4426bafd0314fc390ca95ac05a61b1d529941 Mon Sep 17 00:00:00 2001
From: Jim Jones <[email protected]>
Date: Mon, 2 Nov 2026 10:18:29 +0100
Subject: [PATCH v3 5/5] pg_visibility: Reject temporary tables of other
sessions
pg_visibility cannot see the pages of another session's temporary
table, but it did not reject such tables, so some of its functions
returned wrong results or modified that session's visibility map.
Report an error instead.
Author: Jim Jones <[email protected]>
Reported-by: Michael Paquier <[email protected]>
---
contrib/pg_visibility/pg_visibility.c | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/contrib/pg_visibility/pg_visibility.c b/contrib/pg_visibility/pg_visibility.c
index 4195aeacc2a..86528cacaa2 100644
--- a/contrib/pg_visibility/pg_visibility.c
+++ b/contrib/pg_visibility/pg_visibility.c
@@ -931,6 +931,16 @@ check_relation_relkind(Relation rel)
RelationGetRelationName(rel)),
errdetail_relkind_not_supported(rel->rd_rel->relkind)));
+ /*
+ * Reject attempts to read non-local temporary relations; we would be
+ * likely to get wrong data since we have no visibility into the owning
+ * session's local buffers.
+ */
+ if (RELATION_IS_OTHER_TEMP(rel))
+ ereport(ERROR,
+ (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,
--
2.56.0