From 4f1a1b8c13439625136823a8d6e5bcfa03608a0a Mon Sep 17 00:00:00 2001 From: Pavlo Golub Date: Thu, 5 Feb 2026 12:15:40 +0000 Subject: [PATCH v4] Add pg_current_vxact_id() function This patch introduces pg_current_vxact_id() to expose the current backend's virtual transaction ID 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. Changes in v4: - Rebased onto current master Changes in v3: - Add VXID_FMT macro to lock.h to eliminate format string duplication - Update lockfuncs.c, elog.c, and xid8funcs.c to use VXID_FMT - Fix documentation to use 'localXID' terminology for consistency - Add comprehensive regression tests in xid.sql - Add documentation in func-info.sgml and xact.sgml - Use OID 9538 (in development range 8000-9999) Addresses review comments from Henson Choi. Discussion: https://www.postgresql.org/message-id/CAAAe_zBUd3epVqcDAMVmLDt4-dhxVY5W09%2Bgp5ND_P--b90eeA%40mail.gmail.com --- doc/src/sgml/func/func-info.sgml | 22 +++++++++++++++++++++ doc/src/sgml/xact.sgml | 4 +++- src/backend/utils/adt/lockfuncs.c | 2 +- src/backend/utils/adt/xid8funcs.c | 26 +++++++++++++++++++++++++ src/backend/utils/error/elog.c | 4 ++-- src/include/catalog/pg_proc.dat | 3 +++ src/include/storage/lock.h | 7 +++++++ src/test/regress/expected/xid.out | 32 +++++++++++++++++++++++++++++++ src/test/regress/sql/xid.sql | 13 +++++++++++++ 9 files changed, 109 insertions(+), 4 deletions(-) diff --git a/doc/src/sgml/func/func-info.sgml b/doc/src/sgml/func/func-info.sgml index e3c05e8b933..5ef98b44369 100644 --- a/doc/src/sgml/func/func-info.sgml +++ b/doc/src/sgml/func/func-info.sgml @@ -2865,6 +2865,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/localXID + (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/lockfuncs.c b/src/backend/utils/adt/lockfuncs.c index 3eb9a5b4215..5cdcb941693 100644 --- a/src/backend/utils/adt/lockfuncs.c +++ b/src/backend/utils/adt/lockfuncs.c @@ -80,7 +80,7 @@ VXIDGetDatum(ProcNumber procNumber, LocalTransactionId lxid) */ char vxidstr[32]; - snprintf(vxidstr, sizeof(vxidstr), "%d/%u", procNumber, lxid); + snprintf(vxidstr, sizeof(vxidstr), VXID_FMT, procNumber, lxid); return CStringGetTextDatum(vxidstr); } diff --git a/src/backend/utils/adt/xid8funcs.c b/src/backend/utils/adt/xid8funcs.c index c607e78d9ac..6703dd4d3d1 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), VXID_FMT, + MyProc->vxid.procNumber, MyProc->vxid.lxid); + + PG_RETURN_TEXT_P(cstring_to_text(vxidstr)); +} + /* * pg_current_snapshot() returns pg_snapshot * diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c index b9d2c96b97a..5ef6fc53c56 100644 --- a/src/backend/utils/error/elog.c +++ b/src/backend/utils/error/elog.c @@ -3609,12 +3609,12 @@ log_status_format(StringInfo buf, const char *format, ErrorData *edata) { char strfbuf[128]; - snprintf(strfbuf, sizeof(strfbuf) - 1, "%d/%u", + snprintf(strfbuf, sizeof(strfbuf) - 1, VXID_FMT, MyProc->vxid.procNumber, MyProc->vxid.lxid); appendStringInfo(buf, "%*s", padding, strfbuf); } else - appendStringInfo(buf, "%d/%u", MyProc->vxid.procNumber, MyProc->vxid.lxid); + appendStringInfo(buf, VXID_FMT, MyProc->vxid.procNumber, MyProc->vxid.lxid); } else if (padding != 0) appendStringInfoSpaces(buf, diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 1d2a9db3262..26ffb09cb3c 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -10888,6 +10888,9 @@ { oid => '5066', descr => 'commit status of transaction', proname => 'pg_xact_status', provolatile => 'v', prorettype => 'text', proargtypes => 'xid8', prosrc => 'pg_xact_status' }, +{ oid => '9538', 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/include/storage/lock.h b/src/include/storage/lock.h index ee3cb1dc203..428d186da0e 100644 --- a/src/include/storage/lock.h +++ b/src/include/storage/lock.h @@ -65,6 +65,13 @@ typedef struct VirtualTransactionId LocalTransactionId localTransactionId; /* lxid from PGPROC */ } VirtualTransactionId; +/* + * Format string for VXID display. + * Used by elog.c (%v placeholder), lockfuncs.c (VXIDGetDatum), + * and xid8funcs.c (pg_current_vxact_id). + */ +#define VXID_FMT "%d/%u" + #define InvalidLocalTransactionId 0 #define LocalTransactionIdIsValid(lxid) ((lxid) != InvalidLocalTransactionId) #define VirtualTransactionIdIsValid(vxid) \ 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 base-commit: c16a1b4f2ce8e88ec40d1465c01e327ab8721524 -- 2.55.0