Skip to content

Commit 50f770c

Browse files
committed
Revert GetTransactionSnapshot() to return historic snapshot during LR
Commit 1585ff7 changed GetTransactionSnapshot() to throw an error if it's called during logical decoding, instead of returning the historic snapshot. I made that change for extra protection, because a historic snapshot can only be used to access catalog tables while GetTransactionSnapshot() is usually called when you're executing arbitrary queries. You might get very subtle visibility problems if you tried to use the historic snapshot for arbitrary queries. There's no built-in code in PostgreSQL that calls GetTransactionSnapshot() during logical decoding, but it turns out that the pglogical extension does just that, to evaluate row filter expressions. You would get weird results if the row filter runs arbitrary queries, but it is sane as long as you don't access any non-catalog tables. Even though there are no checks to enforce that in pglogical, a typical row filter expression does not access any tables and works fine. Accessing tables marked with the user_catalog_table = true option is also OK. To fix pglogical with row filters, and any other extensions that might do similar things, revert GetTransactionSnapshot() to return a historic snapshot during logical decoding. To try to still catch the unsafe usage of historic snapshots, add checks in heap_beginscan() and index_beginscan() to complain if you try to use a historic snapshot to scan a non-catalog table. We're very close to the version 18 release however, so add those new checks only in master. Backpatch-through: 18 Reported-by: Noah Misch <noah@leadboat.com> Reviewed-by: Noah Misch <noah@leadboat.com> Discussion: https://www.postgresql.org/message-id/20250809222338.cc.nmisch@google.com
1 parent 16a0039 commit 50f770c

File tree

4 files changed

+35
-4
lines changed

4 files changed

+35
-4
lines changed

src/backend/access/heap/heapam.c

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1143,6 +1143,15 @@ heap_beginscan(Relation relation, Snapshot snapshot,
11431143
if (!(snapshot && IsMVCCSnapshot(snapshot)))
11441144
scan->rs_base.rs_flags &= ~SO_ALLOW_PAGEMODE;
11451145

1146+
/* Check that a historic snapshot is not used for non-catalog tables */
1147+
if (snapshot &&
1148+
IsHistoricMVCCSnapshot(snapshot) &&
1149+
!RelationIsAccessibleInLogicalDecoding(relation))
1150+
{
1151+
elog(ERROR, "cannot query non-catalog table \"%s\" during logical decoding",
1152+
RelationGetRelationName(relation));
1153+
}
1154+
11461155
/*
11471156
* For seqscan and sample scans in a serializable transaction, acquire a
11481157
* predicate lock on the entire relation. This is required not only to

src/backend/access/index/indexam.c

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,14 @@ index_beginscan(Relation heapRelation,
263263

264264
Assert(snapshot != InvalidSnapshot);
265265

266+
/* Check that a historic snapshot is not used for non-catalog tables */
267+
if (IsHistoricMVCCSnapshot(snapshot) &&
268+
!RelationIsAccessibleInLogicalDecoding(heapRelation))
269+
{
270+
elog(ERROR, "cannot query non-catalog table \"%s\" during logical decoding",
271+
RelationGetRelationName(heapRelation));
272+
}
273+
266274
scan = index_beginscan_internal(indexRelation, nkeys, norderbys, snapshot, NULL, false);
267275

268276
/*

src/backend/utils/time/snapmgr.c

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -271,12 +271,23 @@ Snapshot
271271
GetTransactionSnapshot(void)
272272
{
273273
/*
274-
* This should not be called while doing logical decoding. Historic
275-
* snapshots are only usable for catalog access, not for general-purpose
276-
* queries.
274+
* Return historic snapshot if doing logical decoding.
275+
*
276+
* Historic snapshots are only usable for catalog access, not for
277+
* general-purpose queries. The caller is responsible for ensuring that
278+
* the snapshot is used correctly! (PostgreSQL code never calls this
279+
* during logical decoding, but extensions can do it.)
277280
*/
278281
if (HistoricSnapshotActive())
279-
elog(ERROR, "cannot take query snapshot during logical decoding");
282+
{
283+
/*
284+
* We'll never need a non-historic transaction snapshot in this
285+
* (sub-)transaction, so there's no need to be careful to set one up
286+
* for later calls to GetTransactionSnapshot().
287+
*/
288+
Assert(!FirstSnapshotSet);
289+
return HistoricSnapshot;
290+
}
280291

281292
/* First call in transaction? */
282293
if (!FirstSnapshotSet)

src/include/utils/snapmgr.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,9 @@ extern PGDLLIMPORT SnapshotData SnapshotToastData;
5656
((snapshot)->snapshot_type == SNAPSHOT_MVCC || \
5757
(snapshot)->snapshot_type == SNAPSHOT_HISTORIC_MVCC)
5858

59+
#define IsHistoricMVCCSnapshot(snapshot) \
60+
((snapshot)->snapshot_type == SNAPSHOT_HISTORIC_MVCC)
61+
5962
extern Snapshot GetTransactionSnapshot(void);
6063
extern Snapshot GetLatestSnapshot(void);
6164
extern void SnapshotSetCommandId(CommandId curcid);

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy