From fb9c9ac640b469aa8d245ba1055e53292e19d084 Mon Sep 17 00:00:00 2001 From: Pavlo Golub Date: Mon, 8 Dec 2025 11:37:26 +0000 Subject: [PATCH v1] Add pg_current_vxact_id() function to get current virtual transaction ID This patch introduces a new SQL-callable function pg_current_vxact_id() that returns the current backend's virtual transaction ID (VXID) as text in the format 'procNumber/lxid' (e.g., '3/42'). Virtual transaction IDs are always assigned to every transaction, unlike regular XIDs which are only assigned when a transaction modifies data. This makes VXIDs useful for tracking and correlating all transactions, including read-only ones. The VXID format matches what's used in: - elog %v placeholder for logging - pg_locks.virtualtransaction column - Internal PostgreSQL transaction tracking The function returns NULL during recovery or when no valid VXID exists. This provides a clean API for applications that previously had to query pg_locks or parse log files to obtain virtual transaction IDs. --- doc/src/sgml/func/func-info.sgml | 22 +++++++++++++++++++++ doc/src/sgml/xact.sgml | 4 +++- src/backend/utils/adt/xid8funcs.c | 26 +++++++++++++++++++++++++ src/include/catalog/pg_proc.dat | 3 +++ src/test/regress/expected/xid.out | 32 +++++++++++++++++++++++++++++++ src/test/regress/sql/xid.sql | 13 +++++++++++++ 6 files changed, 99 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/func/func-info.sgml b/doc/src/sgml/func/func-info.sgml index d4508114a48..7e5e8e4d31c 100644 --- a/doc/src/sgml/func/func-info.sgml +++ b/doc/src/sgml/func/func-info.sgml @@ -2849,6 +2849,28 @@ acl | {postgres=arwdDxtm/postgres,foo=r/postgres} + + + + pg_current_vxact_id + + pg_current_vxact_id () + text + + + Returns the current virtual transaction ID (VXID) in the + format procNumber/localTransactionId + (for example, 3/42). + Virtual transaction ID is always assigned when a transaction starts, + unlike regular transaction IDs which are only assigned when the + transaction performs a database write. VXIDs are session-scoped and + do not persist across server restarts. They are primarily useful for + correlating transactions with log entries that use the %v placeholder + in . + See for details. + + + diff --git a/doc/src/sgml/xact.sgml b/doc/src/sgml/xact.sgml index 3aa7ee1383e..4a5753178e0 100644 --- a/doc/src/sgml/xact.sgml +++ b/doc/src/sgml/xact.sgml @@ -31,7 +31,9 @@ localXID. For example, the virtual transaction ID 4/12532 has a procNumber of 4 and a localXID of - 12532. + 12532. The function + pg_current_vxact_id returns the current + transaction's VXID. diff --git a/src/backend/utils/adt/xid8funcs.c b/src/backend/utils/adt/xid8funcs.c index 4b3f7a69b3b..fc8e2975c2f 100644 --- a/src/backend/utils/adt/xid8funcs.c +++ b/src/backend/utils/adt/xid8funcs.c @@ -33,6 +33,7 @@ #include "libpq/pqformat.h" #include "miscadmin.h" #include "storage/lwlock.h" +#include "storage/proc.h" #include "storage/procarray.h" #include "storage/procnumber.h" #include "utils/builtins.h" @@ -360,6 +361,31 @@ pg_current_xact_id_if_assigned(PG_FUNCTION_ARGS) PG_RETURN_FULLTRANSACTIONID(topfxid); } +/* + * pg_current_vxact_id() returns text + * + * Return the current virtual transaction ID (vxid). + * vxid is always assigned and available, unlike regular transaction IDs. + * Returns NULL if no valid vxid exists (e.g., during startup/recovery). + */ +Datum +pg_current_vxact_id(PG_FUNCTION_ARGS) +{ + char vxidstr[32]; + + /* + * Check if we have a valid vxid. The vxid format matches what's used + * in elog.c for the %v placeholder and in pg_locks.virtualtransaction. + */ + if (MyProc == NULL || MyProc->vxid.procNumber == INVALID_PROC_NUMBER) + PG_RETURN_NULL(); + + snprintf(vxidstr, sizeof(vxidstr), "%d/%u", + MyProc->vxid.procNumber, MyProc->vxid.lxid); + + PG_RETURN_TEXT_P(cstring_to_text(vxidstr)); +} + /* * pg_current_snapshot() returns pg_snapshot * diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fd9448ec7b9..9777c69102e 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -10676,6 +10676,9 @@ { oid => '5066', descr => 'commit status of transaction', proname => 'pg_xact_status', provolatile => 'v', prorettype => 'text', proargtypes => 'xid8', prosrc => 'pg_xact_status' }, +{ oid => '5101', descr => 'get current virtual transaction ID', + proname => 'pg_current_vxact_id', provolatile => 's', proparallel => 'u', + prorettype => 'text', proargtypes => '', prosrc => 'pg_current_vxact_id' }, # record comparison using normal comparison rules { oid => '2981', diff --git a/src/test/regress/expected/xid.out b/src/test/regress/expected/xid.out index 1ce7826cf90..55649cc4358 100644 --- a/src/test/regress/expected/xid.out +++ b/src/test/regress/expected/xid.out @@ -463,6 +463,38 @@ SELECT pg_current_xact_id_if_assigned() IS NOT DISTINCT FROM xid8 :'pg_current_x t (1 row) +COMMIT; +-- test pg_current_vxact_id +BEGIN; +SELECT pg_current_vxact_id() IS NOT NULL AS vxid_assigned; + vxid_assigned +--------------- + t +(1 row) + +SELECT pg_current_vxact_id() ~ '^\d+/\d+$' AS vxid_format_ok; + vxid_format_ok +---------------- + t +(1 row) + +SELECT pg_current_vxact_id() AS vxid1 \gset +SELECT pg_current_vxact_id() = :'vxid1' AS vxid_stable; + vxid_stable +------------- + t +(1 row) + +COMMIT; +-- start new transaction, vxid should change +BEGIN; +SELECT pg_current_vxact_id() AS vxid2 \gset +SELECT :'vxid2' <> :'vxid1' AS vxid_changed; + vxid_changed +-------------- + t +(1 row) + COMMIT; -- test xid status functions BEGIN; diff --git a/src/test/regress/sql/xid.sql b/src/test/regress/sql/xid.sql index 9f716b3653a..5a48f914a1f 100644 --- a/src/test/regress/sql/xid.sql +++ b/src/test/regress/sql/xid.sql @@ -132,6 +132,19 @@ SELECT pg_current_xact_id() \gset SELECT pg_current_xact_id_if_assigned() IS NOT DISTINCT FROM xid8 :'pg_current_xact_id'; COMMIT; +-- test pg_current_vxact_id +BEGIN; +SELECT pg_current_vxact_id() IS NOT NULL AS vxid_assigned; +SELECT pg_current_vxact_id() ~ '^\d+/\d+$' AS vxid_format_ok; +SELECT pg_current_vxact_id() AS vxid1 \gset +SELECT pg_current_vxact_id() = :'vxid1' AS vxid_stable; +COMMIT; +-- start new transaction, vxid should change +BEGIN; +SELECT pg_current_vxact_id() AS vxid2 \gset +SELECT :'vxid2' <> :'vxid1' AS vxid_changed; +COMMIT; + -- test xid status functions BEGIN; SELECT pg_current_xact_id() AS committed \gset -- 2.52.0