v1-0001-add-pg_current_vxact_id-function.patch
application/octet-stream
Filename: v1-0001-add-pg_current_vxact_id-function.patch
Type: application/octet-stream
Part: 0
From fb9c9ac640b469aa8d245ba1055e53292e19d084 Mon Sep 17 00:00:00 2001
From: Pavlo Golub <pavlo.golub@cybertec.at>
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}
</para></entry>
</row>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
+ <primary>pg_current_vxact_id</primary>
+ </indexterm>
+ <function>pg_current_vxact_id</function> ()
+ <returnvalue>text</returnvalue>
+ </para>
+ <para>
+ Returns the current virtual transaction ID (VXID) in the
+ format <literal>procNumber/localTransactionId</literal>
+ (for example, <literal>3/42</literal>).
+ 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 <literal>%v</literal> placeholder
+ in <xref linkend="guc-log-line-prefix"/>.
+ See <xref linkend="transaction-id"/> for details.
+ </para></entry>
+ </row>
+
<row>
<entry role="func_table_entry"><para role="func_signature">
<indexterm>
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 @@
<literal>localXID</literal>. For example, the virtual transaction
ID <literal>4/12532</literal> has a <literal>procNumber</literal>
of <literal>4</literal> and a <literal>localXID</literal> of
- <literal>12532</literal>.
+ <literal>12532</literal>. The function
+ <function>pg_current_vxact_id</function> returns the current
+ transaction's VXID.
</para>
<para>
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