On 29 August 2016 at 11:45, Andres Freund <and...@anarazel.de> wrote: > Hi, > > On 2016-08-29 11:25:39 +0800, Craig Ringer wrote: >> ERROR: could not access status of transaction 778793573 >> DETAIL: could not open file "pg_clog/02E6": No such file or directory >> >> What I'd really like is to be able to ask transam.c to handle the >> xid_in_recent_past logic, treating an attempt to read an xid from >> beyond the clog truncation threshold as a soft error indicating >> unknown xact state. But that involves delving into slru.c, and I >> really, really don't want to touch that for what should be a simple >> and pretty noninvasive utility function. > > Can't you "just" check this against ShmemVariableCache->oldestXid while > holding appropriate locks?
Hm. Yeah, I should've thought of that. Thank you. >> A PG_TRY to trap ERRCODE_UNDEFINED_FILE seems like it'd be sufficient, >> except for two issues: > > It seems like a bad idea to PG_CATCH and not re-throw an error. That > generally is quite error prone. At the very least locking and such gets > a lot more complicated (as you noticed below). Yeah, and as I remember from the "fun" of trying to write apply errors to tables in BDR. It wasn't my first choice. >> * TransactionIdGetStatus() releases the CLogControlLock taken by >> SimpleLruReadPage_ReadOnly() on normal exit but not after an exception >> thrown from SlruReportIOError(). It seems appropriate for >> SimpleLruReadPage() to release the LWLock before calling >> SlruReportIOError(), so I've done that as a separate patch (now 0001). > > We normally prefer to handle this via the "bulk" releases in the error > handlers. It's otherwise hard to write code that handles these > situations reliably. It's different for spinlocks, but those normally > protect far smaller regions of code. Fair enough. It's not a complex path, but there are a _lot_ of callers, and while I can't really imagine any of them relying on the CLogControLock being held on error it's not something I was keen to change. I thought complicating the clog with a soft-error interface was worse and didn't come up with a better approach. Said better approach attached in revised series. Thanks. My only real complaint with doing this is that it's a bit more conservative. But in practice clog truncation probably won't follow that far behind oldestXmin so except in fairly contrived circumstances it won't hurt. Apps that need guarantees about how old an xid they can get status on can hold down xmin with a replication slot, a dummy prepared xact, or whatever. If we find that becomes a common need that should be made simpler then appropriate API to allow apps to hold down clog truncation w/o blocking vacuuming can be added down the track. -- Craig Ringer http://www.2ndQuadrant.com/ PostgreSQL Development, 24x7 Support, Training & Services
From b69f99b63f667e745ccdee2130ee1b50690109d4 Mon Sep 17 00:00:00 2001 From: Craig Ringer <cr...@2ndquadrant.com> Date: Fri, 19 Aug 2016 14:44:15 +0800 Subject: [PATCH 1/3] Introduce txid_status(bigint) to get status of an xact If an appliation is disconnected while a COMMIT request is in flight, the backend crashes mid-commit, etc, then an application may not be sure whether or not a commit completed successfully or was rolled back. While two-phase commit solves this it does so at a considerable overhead, so introduce a lighter alternative. txid_status(bigint) lets an application determine the status of a a commit based on an xid-with-epoch as returned by txid_current() or similar. Status may be committed, aborted, in-progress (including prepared xacts) or null if the xact is too old for its commit status to still be retained because it has passed the wrap-around epoch boundary. Applications must call txid_current() in their transactions to make much use of this since PostgreSQL does not automatically report an xid to the client when one is assigned. --- doc/src/sgml/func.sgml | 31 ++++++++++ src/backend/access/transam/clog.c | 23 ------- src/backend/utils/adt/txid.c | 119 +++++++++++++++++++++++++++++++++++++ src/include/access/clog.h | 23 +++++++ src/include/catalog/pg_proc.h | 2 + src/include/utils/builtins.h | 1 + src/test/regress/expected/txid.out | 68 +++++++++++++++++++++ src/test/regress/sql/txid.sql | 38 ++++++++++++ 8 files changed, 282 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index 5148095..d8b086f 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -17143,6 +17143,10 @@ SELECT collation for ('foo' COLLATE "de_DE"); <primary>txid_visible_in_snapshot</primary> </indexterm> + <indexterm> + <primary>txid_status</primary> + </indexterm> + <para> The functions shown in <xref linkend="functions-txid-snapshot"> provide server transaction information in an exportable form. The main @@ -17193,6 +17197,11 @@ SELECT collation for ('foo' COLLATE "de_DE"); <entry><type>boolean</type></entry> <entry>is transaction ID visible in snapshot? (do not use with subtransaction ids)</entry> </row> + <row> + <entry><literal><function>txid_status(<parameter>bigint</parameter>)</function></literal></entry> + <entry><type>txid_status</type></entry> + <entry>report the status of the given xact - <literal>committed</literal>, <literal>aborted</literal>, <literal>in progress</literal>, or NULL if the xid is too old</entry> + </row> </tbody> </tgroup> </table> @@ -17263,6 +17272,28 @@ SELECT collation for ('foo' COLLATE "de_DE"); </para> <para> + <function>txid_status(bigint)</> reports the commit status of a recent + transaction. Applications may use it to determine whether a transaction + committed or aborted when the application and/or database server crashed or + lost connection while a <literal>COMMIT</literal> command was in progress. + The status of a transaction will be reported as one of: + <itemizedlist> + <listitem><para><literal>'in progress'</></></> + <listitem><para><literal>'committed'</></></> + <listitem><para><literal>'aborted'</></></> + <listitem><para><literal>NULL</> if xid too old</></> + </itemizedlist> + PostgreSQL discards the commit status transactions after no references to + the transaction survive in other active transactions, tables, replication + slots, etc. This means that the status of older transactions cannot be + determined. <function>txid_status(bigint)</> returns <literal>NULL</> if a + transaction is too old to look up. Prepared transactions are reported as + <literal>in progress</>; applications must check <link + linkend="view-pg-prepared-xacts"><literal>pg_prepared_xacts</></> if they + need to determine if the xid is a prepared transaction. + </para> + + <para> The functions shown in <xref linkend="functions-commit-timestamp"> provide information about transactions that have been already committed. These functions mainly provide information about when the transactions diff --git a/src/backend/access/transam/clog.c b/src/backend/access/transam/clog.c index 2634476..1a6e26d 100644 --- a/src/backend/access/transam/clog.c +++ b/src/backend/access/transam/clog.c @@ -41,29 +41,6 @@ #include "miscadmin.h" #include "pg_trace.h" -/* - * Defines for CLOG page sizes. A page is the same BLCKSZ as is used - * everywhere else in Postgres. - * - * Note: because TransactionIds are 32 bits and wrap around at 0xFFFFFFFF, - * CLOG page numbering also wraps around at 0xFFFFFFFF/CLOG_XACTS_PER_PAGE, - * and CLOG segment numbering at - * 0xFFFFFFFF/CLOG_XACTS_PER_PAGE/SLRU_PAGES_PER_SEGMENT. We need take no - * explicit notice of that fact in this module, except when comparing segment - * and page numbers in TruncateCLOG (see CLOGPagePrecedes). - */ - -/* We need two bits per xact, so four xacts fit in a byte */ -#define CLOG_BITS_PER_XACT 2 -#define CLOG_XACTS_PER_BYTE 4 -#define CLOG_XACTS_PER_PAGE (BLCKSZ * CLOG_XACTS_PER_BYTE) -#define CLOG_XACT_BITMASK ((1 << CLOG_BITS_PER_XACT) - 1) - -#define TransactionIdToPage(xid) ((xid) / (TransactionId) CLOG_XACTS_PER_PAGE) -#define TransactionIdToPgIndex(xid) ((xid) % (TransactionId) CLOG_XACTS_PER_PAGE) -#define TransactionIdToByte(xid) (TransactionIdToPgIndex(xid) / CLOG_XACTS_PER_BYTE) -#define TransactionIdToBIndex(xid) ((xid) % (TransactionId) CLOG_XACTS_PER_BYTE) - /* We store the latest async LSN for each group of transactions */ #define CLOG_XACTS_PER_LSN_GROUP 32 /* keep this a power of 2 */ #define CLOG_LSNS_PER_PAGE (CLOG_XACTS_PER_PAGE / CLOG_XACTS_PER_LSN_GROUP) diff --git a/src/backend/utils/adt/txid.c b/src/backend/utils/adt/txid.c index 276075e..69148a5 100644 --- a/src/backend/utils/adt/txid.c +++ b/src/backend/utils/adt/txid.c @@ -21,6 +21,7 @@ #include "postgres.h" +#include "access/clog.h" #include "access/transam.h" #include "access/xact.h" #include "access/xlog.h" @@ -117,6 +118,67 @@ convert_xid(TransactionId xid, const TxidEpoch *state) } /* + * Helper to get a TransactionId from a 64-bit txid with wraparound + * detection. + * + * ERRORs if the txid is in the future. Returns permanent XIDs + * unchanged. Otherwise returns the 32-bit xid and sets the too_old + * param to true if status for this xid cannot be reliably determined. + * It's only safe to use the returned xid for most purposes if too_old + * is false on return. + * + * XIDs older than ShmemVariableCache->oldestXid are treated as too + * old to look up because the clog could've been truncated away - even + * if they're still far from the xid wraparound theshold. The caller + * should have at least a share lock on XidGenLock to prevent + * oldestXid from advancing between our oldestXid check and subsequent + * lookups of transaction status using the returned xid. Failure to do + * so risks ERRORs on clog access but nothing worse. + */ +static TransactionId +get_xid_in_recent_past(txid xid_with_epoch, bool *too_old) +{ + uint32 xid_epoch = (uint32) (xid_with_epoch >> 32); + TransactionId xid = (TransactionId) xid_with_epoch; + TxidEpoch now_epoch; + + load_xid_epoch(&now_epoch); + + *too_old = false; + + if (!TransactionIdIsNormal(xid)) + { + /* must be a permanent XID, ignore the epoch and return unchanged */ + return xid; + } + else if (xid_epoch > now_epoch.epoch + || (xid_epoch == now_epoch.epoch && xid > now_epoch.last_xid)) + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("transaction ID "UINT64_FORMAT" is in the future", + xid_with_epoch))); + } + else if (xid_epoch + 1 < now_epoch.epoch + || (xid_epoch + 1 == now_epoch.epoch && xid < now_epoch.last_xid)) + { + /* xid is wrapped, too far in the past */ + *too_old = true; + } + else if (TransactionIdPrecedes(xid, ShmemVariableCache->oldestXid)) + { + /* xid isn't wrapped, but clog could've been truncated away */ + *too_old = true; + } + else + { + Assert(TransactionIdPrecedesOrEquals(xid, now_epoch.last_xid)); + } + + return xid; +} + +/* * txid comparator for qsort/bsearch */ static int @@ -354,6 +416,9 @@ bad_format: * * Return the current toplevel transaction ID as TXID * If the current transaction does not have one, one is assigned. + * + * This value has the epoch as the high 32 bits and the 32-bit xid + * as the low 32 bits. */ Datum txid_current(PG_FUNCTION_ARGS) @@ -658,3 +723,57 @@ txid_snapshot_xip(PG_FUNCTION_ARGS) SRF_RETURN_DONE(fctx); } } + +/* + * Report the status of a recent transaction ID, or null for wrapped, + * truncated away or otherwise too old XIDs. + */ +Datum +txid_status(PG_FUNCTION_ARGS) +{ + const char *status; + bool too_old; + uint64 xid_with_epoch = PG_GETARG_INT64(0); + TransactionId xid; + + /* + * We must hold XidGenLock here to prevent oldestXid advancing and + * triggering clog truncation between when we check that the xid + * is ok and when we look it up in the clog. Otherwise an + * exception might get thrown on clog access. + */ + LWLockAcquire(XidGenLock, LW_SHARED); + xid = get_xid_in_recent_past(xid_with_epoch, &too_old); + + if (!TransactionIdIsValid(xid)) + { + LWLockRelease(XidGenLock); + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("transaction ID "UINT64_FORMAT" is an invalid xid", + xid_with_epoch))); + } + + if (too_old) + status = NULL; + else if (TransactionIdIsCurrentTransactionId(xid)) + status = gettext_noop("in progress"); + else if (TransactionIdDidCommit(xid)) + status = gettext_noop("committed"); + else if (TransactionIdDidAbort(xid)) + status = gettext_noop("aborted"); + else + /* + * can't test TransactionIdIsInProgress here or we race + * with concurrent commit/abort. There's no point anyway, + * since it might then commit/abort just after we check. + */ + status = gettext_noop("in progress"); + + LWLockRelease(XidGenLock); + + if (status == NULL) + PG_RETURN_NULL(); + else + PG_RETURN_TEXT_P(cstring_to_text(status)); +} diff --git a/src/include/access/clog.h b/src/include/access/clog.h index 06c069a..a763dfb 100644 --- a/src/include/access/clog.h +++ b/src/include/access/clog.h @@ -28,6 +28,29 @@ typedef int XidStatus; #define TRANSACTION_STATUS_ABORTED 0x02 #define TRANSACTION_STATUS_SUB_COMMITTED 0x03 +/* + * Defines for CLOG page sizes. A page is the same BLCKSZ as is used + * everywhere else in Postgres. + * + * Note: because TransactionIds are 32 bits and wrap around at 0xFFFFFFFF, + * CLOG page numbering also wraps around at 0xFFFFFFFF/CLOG_XACTS_PER_PAGE, + * and CLOG segment numbering at + * 0xFFFFFFFF/CLOG_XACTS_PER_PAGE/SLRU_PAGES_PER_SEGMENT. We need take no + * explicit notice of that fact in this module, except when comparing segment + * and page numbers in TruncateCLOG (see CLOGPagePrecedes). + */ + +/* We need two bits per xact, so four xacts fit in a byte */ +#define CLOG_BITS_PER_XACT 2 +#define CLOG_XACTS_PER_BYTE 4 +#define CLOG_XACTS_PER_PAGE (BLCKSZ * CLOG_XACTS_PER_BYTE) +#define CLOG_XACT_BITMASK ((1 << CLOG_BITS_PER_XACT) - 1) + +#define TransactionIdToPage(xid) ((xid) / (TransactionId) CLOG_XACTS_PER_PAGE) +#define TransactionIdToPgIndex(xid) ((xid) % (TransactionId) CLOG_XACTS_PER_PAGE) +#define TransactionIdToByte(xid) (TransactionIdToPgIndex(xid) / CLOG_XACTS_PER_BYTE) +#define TransactionIdToBIndex(xid) ((xid) % (TransactionId) CLOG_XACTS_PER_BYTE) + extern void TransactionIdSetTreeStatus(TransactionId xid, int nsubxids, TransactionId *subxids, XidStatus status, XLogRecPtr lsn); diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h index e2d08ba..0ad870c 100644 --- a/src/include/catalog/pg_proc.h +++ b/src/include/catalog/pg_proc.h @@ -4928,6 +4928,8 @@ DATA(insert OID = 2947 ( txid_snapshot_xip PGNSP PGUID 12 1 50 0 0 f f f f t DESCR("get set of in-progress txids in snapshot"); DATA(insert OID = 2948 ( txid_visible_in_snapshot PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0 16 "20 2970" _null_ _null_ _null_ _null_ _null_ txid_visible_in_snapshot _null_ _null_ _null_ )); DESCR("is txid visible in snapshot?"); +DATA(insert OID = 3346 ( txid_status PGNSP PGUID 12 1 0 0 0 f f f f t f v s 1 0 25 "20" _null_ _null_ _null_ _null_ _null_ txid_status _null_ _null_ _null_ )); +DESCR("commit status of transaction"); /* record comparison using normal comparison rules */ DATA(insert OID = 2981 ( record_eq PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0 16 "2249 2249" _null_ _null_ _null_ _null_ _null_ record_eq _null_ _null_ _null_ )); diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h index 2ae212a..baffa38 100644 --- a/src/include/utils/builtins.h +++ b/src/include/utils/builtins.h @@ -1227,6 +1227,7 @@ extern Datum txid_snapshot_xmin(PG_FUNCTION_ARGS); extern Datum txid_snapshot_xmax(PG_FUNCTION_ARGS); extern Datum txid_snapshot_xip(PG_FUNCTION_ARGS); extern Datum txid_visible_in_snapshot(PG_FUNCTION_ARGS); +extern Datum txid_status(PG_FUNCTION_ARGS); /* uuid.c */ extern Datum uuid_in(PG_FUNCTION_ARGS); diff --git a/src/test/regress/expected/txid.out b/src/test/regress/expected/txid.out index 802ccb9..015dae3 100644 --- a/src/test/regress/expected/txid.out +++ b/src/test/regress/expected/txid.out @@ -254,3 +254,71 @@ SELECT txid_current_if_assigned() IS NOT DISTINCT FROM BIGINT :'txid_current'; (1 row) COMMIT; +-- test xid status functions +BEGIN; +SELECT txid_current() AS committed \gset +COMMIT; +BEGIN; +SELECT txid_current() AS rolledback \gset +ROLLBACK; +BEGIN; +SELECT txid_current() AS inprogress \gset +SELECT txid_status(:committed) AS committed; + committed +----------- + committed +(1 row) + +SELECT txid_status(:rolledback) AS rolledback; + rolledback +------------ + aborted +(1 row) + +SELECT txid_status(:inprogress) AS inprogress; + inprogress +------------- + in progress +(1 row) + +SELECT txid_status(1); -- BootstrapTransactionId is always committed + txid_status +------------- + committed +(1 row) + +SELECT txid_status(2); -- FrozenTransactionId is always committed + txid_status +------------- + committed +(1 row) + +SELECT txid_status(3); -- in regress testing FirstNormalTransactionId will always be behind oldestXmin + txid_status +------------- + +(1 row) + +COMMIT; +BEGIN; +CREATE FUNCTION test_future_xid_status(bigint) +RETURNS void +LANGUAGE plpgsql +AS +$$ +BEGIN + PERFORM txid_status($1); + RAISE EXCEPTION 'didn''t ERROR at xid in the future as expected'; +EXCEPTION + WHEN invalid_parameter_value THEN + RAISE NOTICE 'Got expected error for xid in the future'; +END; +$$; +SELECT test_future_xid_status(:inprogress + 10000); +NOTICE: Got expected error for xid in the future + test_future_xid_status +------------------------ + +(1 row) + +ROLLBACK; diff --git a/src/test/regress/sql/txid.sql b/src/test/regress/sql/txid.sql index 4aefd9e..bd6decf 100644 --- a/src/test/regress/sql/txid.sql +++ b/src/test/regress/sql/txid.sql @@ -59,3 +59,41 @@ SELECT txid_current_if_assigned() IS NULL; SELECT txid_current() \gset SELECT txid_current_if_assigned() IS NOT DISTINCT FROM BIGINT :'txid_current'; COMMIT; + +-- test xid status functions +BEGIN; +SELECT txid_current() AS committed \gset +COMMIT; + +BEGIN; +SELECT txid_current() AS rolledback \gset +ROLLBACK; + +BEGIN; +SELECT txid_current() AS inprogress \gset + +SELECT txid_status(:committed) AS committed; +SELECT txid_status(:rolledback) AS rolledback; +SELECT txid_status(:inprogress) AS inprogress; +SELECT txid_status(1); -- BootstrapTransactionId is always committed +SELECT txid_status(2); -- FrozenTransactionId is always committed +SELECT txid_status(3); -- in regress testing FirstNormalTransactionId will always be behind oldestXmin + +COMMIT; + +BEGIN; +CREATE FUNCTION test_future_xid_status(bigint) +RETURNS void +LANGUAGE plpgsql +AS +$$ +BEGIN + PERFORM txid_status($1); + RAISE EXCEPTION 'didn''t ERROR at xid in the future as expected'; +EXCEPTION + WHEN invalid_parameter_value THEN + RAISE NOTICE 'Got expected error for xid in the future'; +END; +$$; +SELECT test_future_xid_status(:inprogress + 10000); +ROLLBACK; -- 2.5.5
From ae1b034b9600901afaebc8b67f1a5c233c82e13e Mon Sep 17 00:00:00 2001 From: Craig Ringer <cr...@2ndquadrant.com> Date: Fri, 19 Aug 2016 14:49:52 +0800 Subject: [PATCH 2/3] Add txid_convert_if_recent() to get the 32-bit xid from a bigint xid txid_current() returns an epoch-extended 64-bit xid as a bigint, but many PostgreSQL functions take and many views report the narrow 32-bit 'xid' type that's subject to wrap-around. To compare these apps must currently bit-shift the 64-bit xid down and they have no way to check the epoch. Add a function that returns the downshifted xid if it's in the current epoch, or null if the xid is too far in the past and cannot be compared with any 'xid' value in the current server epoch. --- doc/src/sgml/func.sgml | 17 ++++- src/backend/utils/adt/txid.c | 12 ++++ src/include/catalog/pg_proc.h | 4 +- src/include/utils/builtins.h | 1 + src/test/regress/expected/alter_table.out | 4 +- src/test/regress/expected/txid.out | 100 ++++++++++++++++++++++++++++++ src/test/regress/sql/alter_table.sql | 4 +- src/test/regress/sql/txid.sql | 51 +++++++++++++++ 8 files changed, 185 insertions(+), 8 deletions(-) diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index d8b086f..fe3325b 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -17198,6 +17198,11 @@ SELECT collation for ('foo' COLLATE "de_DE"); <entry>is transaction ID visible in snapshot? (do not use with subtransaction ids)</entry> </row> <row> + <entry><literal><function>txid_convert_if_recent(<parameter>bigint</parameter>)</function></literal></entry> + <entry><type>xid</type></entry> + <entry>return the 32-bit <type>xid</> for a 64-bit transaction ID if it isn't wrapped around, otherwise return null</entry> + </row> + <row> <entry><literal><function>txid_status(<parameter>bigint</parameter>)</function></literal></entry> <entry><type>txid_status</type></entry> <entry>report the status of the given xact - <literal>committed</literal>, <literal>aborted</literal>, <literal>in progress</literal>, or NULL if the xid is too old</entry> @@ -17210,9 +17215,15 @@ SELECT collation for ('foo' COLLATE "de_DE"); The internal transaction ID type (<type>xid</>) is 32 bits wide and wraps around every 4 billion transactions. However, these functions export a 64-bit format that is extended with an <quote>epoch</> counter - so it will not wrap around during the life of an installation. - The data type used by these functions, <type>txid_snapshot</type>, - stores information about transaction ID + so it will not wrap around during the life of an installation. For that + reason you cannot cast a bigint transaction ID directly to <type>xid</> + and must use <function>txid_convert_if_recent(bigint)</function> instead of + casting to <type>xid</>. + </para> + + <para> + The data type used by the xid snapshot functions, + <type>txid_snapshot</type>, stores information about transaction ID visibility at a particular moment in time. Its components are described in <xref linkend="functions-txid-snapshot-parts">. </para> diff --git a/src/backend/utils/adt/txid.c b/src/backend/utils/adt/txid.c index 69148a5..8052f61 100644 --- a/src/backend/utils/adt/txid.c +++ b/src/backend/utils/adt/txid.c @@ -724,6 +724,18 @@ txid_snapshot_xip(PG_FUNCTION_ARGS) } } +Datum +txid_convert_if_recent(PG_FUNCTION_ARGS) +{ + bool wraparound; + TransactionId xid = get_xid_in_recent_past(PG_GETARG_INT64(0), &wraparound); + + if (wraparound) + PG_RETURN_NULL(); + else + return TransactionIdGetDatum(xid); +} + /* * Report the status of a recent transaction ID, or null for wrapped, * truncated away or otherwise too old XIDs. diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h index 0ad870c..59fa907 100644 --- a/src/include/catalog/pg_proc.h +++ b/src/include/catalog/pg_proc.h @@ -4928,8 +4928,10 @@ DATA(insert OID = 2947 ( txid_snapshot_xip PGNSP PGUID 12 1 50 0 0 f f f f t DESCR("get set of in-progress txids in snapshot"); DATA(insert OID = 2948 ( txid_visible_in_snapshot PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0 16 "20 2970" _null_ _null_ _null_ _null_ _null_ txid_visible_in_snapshot _null_ _null_ _null_ )); DESCR("is txid visible in snapshot?"); -DATA(insert OID = 3346 ( txid_status PGNSP PGUID 12 1 0 0 0 f f f f t f v s 1 0 25 "20" _null_ _null_ _null_ _null_ _null_ txid_status _null_ _null_ _null_ )); +DATA(insert OID = 3347 ( txid_status PGNSP PGUID 12 1 0 0 0 f f f f t f v s 1 0 25 "20" _null_ _null_ _null_ _null_ _null_ txid_status _null_ _null_ _null_ )); DESCR("commit status of transaction"); +DATA(insert OID = 3344 ( txid_convert_if_recent PGNSP PGUID 12 1 0 0 0 f f f f t f v s 1 0 28 "20" _null_ _null_ _null_ _null_ _null_ txid_convert_if_recent _null_ _null_ _null_ )); +DESCR("get the xid from a bigint transaction id if not wrapped around"); /* record comparison using normal comparison rules */ DATA(insert OID = 2981 ( record_eq PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0 16 "2249 2249" _null_ _null_ _null_ _null_ _null_ record_eq _null_ _null_ _null_ )); diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h index baffa38..a95a50f 100644 --- a/src/include/utils/builtins.h +++ b/src/include/utils/builtins.h @@ -1228,6 +1228,7 @@ extern Datum txid_snapshot_xmax(PG_FUNCTION_ARGS); extern Datum txid_snapshot_xip(PG_FUNCTION_ARGS); extern Datum txid_visible_in_snapshot(PG_FUNCTION_ARGS); extern Datum txid_status(PG_FUNCTION_ARGS); +extern Datum txid_convert_if_recent(PG_FUNCTION_ARGS); /* uuid.c */ extern Datum uuid_in(PG_FUNCTION_ARGS); diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out index 3232cda..3bdbb87 100644 --- a/src/test/regress/expected/alter_table.out +++ b/src/test/regress/expected/alter_table.out @@ -2029,7 +2029,7 @@ from pg_locks l join pg_class c on l.relation = c.oid where virtualtransaction = ( select virtualtransaction from pg_locks - where transactionid = txid_current()::integer) + where transactionid is not distinct from txid_convert_if_recent(txid_current()) ) and locktype = 'relation' and relnamespace != (select oid from pg_namespace where nspname = 'pg_catalog') and c.relname != 'my_locks' @@ -2192,7 +2192,7 @@ from pg_locks l join pg_class c on l.relation = c.oid where virtualtransaction = ( select virtualtransaction from pg_locks - where transactionid = txid_current()::integer) + where transactionid is not distinct from txid_convert_if_recent(txid_current()) ) and locktype = 'relation' and relnamespace != (select oid from pg_namespace where nspname = 'pg_catalog') and c.relname = 'my_locks' diff --git a/src/test/regress/expected/txid.out b/src/test/regress/expected/txid.out index 015dae3..3e3075d 100644 --- a/src/test/regress/expected/txid.out +++ b/src/test/regress/expected/txid.out @@ -263,6 +263,63 @@ SELECT txid_current() AS rolledback \gset ROLLBACK; BEGIN; SELECT txid_current() AS inprogress \gset +-- We can reasonably assume we haven't hit the first xid +-- wraparound here, so: +SELECT txid_convert_if_recent(:committed) = :'committed'::xid; + ?column? +---------- + t +(1 row) + +SELECT txid_convert_if_recent(:rolledback) = :'rolledback'::xid; + ?column? +---------- + t +(1 row) + +SELECT txid_convert_if_recent(:inprogress) = :'inprogress'::xid; + ?column? +---------- + t +(1 row) + +SELECT txid_convert_if_recent(0) = '0'::xid; -- InvalidTransactionId + ?column? +---------- + t +(1 row) + +SELECT txid_convert_if_recent(1) = '1'::xid; -- BootstrapTransactionId + ?column? +---------- + t +(1 row) + +SELECT txid_convert_if_recent(2) = '2'::xid; -- FrozenTransactionId + ?column? +---------- + t +(1 row) + +-- we ignore epoch for the fixed xids +SELECT txid_convert_if_recent(BIGINT '1' << 32); + txid_convert_if_recent +------------------------ + 0 +(1 row) + +SELECT txid_convert_if_recent((BIGINT '1' << 32) + 1); + txid_convert_if_recent +------------------------ + 1 +(1 row) + +SELECT txid_convert_if_recent((BIGINT '1' << 32) + 2); + txid_convert_if_recent +------------------------ + 2 +(1 row) + SELECT txid_status(:committed) AS committed; committed ----------- @@ -300,6 +357,49 @@ SELECT txid_status(3); -- in regress testing FirstNormalTransactionId will alway (1 row) COMMIT; +-- Check xids in the future +DO +$$ +BEGIN + PERFORM txid_convert_if_recent(txid_current() + (BIGINT '1' << 32)); +EXCEPTION + WHEN invalid_parameter_value THEN + RAISE NOTICE 'got expected xid out of range error'; +END; +$$; +NOTICE: got expected xid out of range error +DO +$$ +BEGIN + PERFORM txid_convert_if_recent((BIGINT '1' << 32) - 1); +EXCEPTION + WHEN invalid_parameter_value THEN + RAISE NOTICE 'got expected xid out of range error'; +END; +$$; +NOTICE: got expected xid out of range error +BEGIN; +CREATE FUNCTION test_future_xid(bigint) +RETURNS void +LANGUAGE plpgsql +AS +$$ +BEGIN + PERFORM txid_convert_if_recent($1); + RAISE EXCEPTION 'didn''t ERROR at xid in the future as expected'; +EXCEPTION + WHEN invalid_parameter_value THEN + RAISE NOTICE 'Got expected error for xid in the future'; +END; +$$; +SELECT test_future_xid(:inprogress + 100000); +NOTICE: Got expected error for xid in the future + test_future_xid +----------------- + +(1 row) + +ROLLBACK; BEGIN; CREATE FUNCTION test_future_xid_status(bigint) RETURNS void diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql index 72e65d4..124d71f 100644 --- a/src/test/regress/sql/alter_table.sql +++ b/src/test/regress/sql/alter_table.sql @@ -1335,7 +1335,7 @@ from pg_locks l join pg_class c on l.relation = c.oid where virtualtransaction = ( select virtualtransaction from pg_locks - where transactionid = txid_current()::integer) + where transactionid is not distinct from txid_convert_if_recent(txid_current()) ) and locktype = 'relation' and relnamespace != (select oid from pg_namespace where nspname = 'pg_catalog') and c.relname != 'my_locks' @@ -1422,7 +1422,7 @@ from pg_locks l join pg_class c on l.relation = c.oid where virtualtransaction = ( select virtualtransaction from pg_locks - where transactionid = txid_current()::integer) + where transactionid is not distinct from txid_convert_if_recent(txid_current()) ) and locktype = 'relation' and relnamespace != (select oid from pg_namespace where nspname = 'pg_catalog') and c.relname = 'my_locks' diff --git a/src/test/regress/sql/txid.sql b/src/test/regress/sql/txid.sql index bd6decf..cb22007 100644 --- a/src/test/regress/sql/txid.sql +++ b/src/test/regress/sql/txid.sql @@ -72,6 +72,19 @@ ROLLBACK; BEGIN; SELECT txid_current() AS inprogress \gset +-- We can reasonably assume we haven't hit the first xid +-- wraparound here, so: +SELECT txid_convert_if_recent(:committed) = :'committed'::xid; +SELECT txid_convert_if_recent(:rolledback) = :'rolledback'::xid; +SELECT txid_convert_if_recent(:inprogress) = :'inprogress'::xid; +SELECT txid_convert_if_recent(0) = '0'::xid; -- InvalidTransactionId +SELECT txid_convert_if_recent(1) = '1'::xid; -- BootstrapTransactionId +SELECT txid_convert_if_recent(2) = '2'::xid; -- FrozenTransactionId +-- we ignore epoch for the fixed xids +SELECT txid_convert_if_recent(BIGINT '1' << 32); +SELECT txid_convert_if_recent((BIGINT '1' << 32) + 1); +SELECT txid_convert_if_recent((BIGINT '1' << 32) + 2); + SELECT txid_status(:committed) AS committed; SELECT txid_status(:rolledback) AS rolledback; SELECT txid_status(:inprogress) AS inprogress; @@ -81,6 +94,44 @@ SELECT txid_status(3); -- in regress testing FirstNormalTransactionId will alway COMMIT; +-- Check xids in the future +DO +$$ +BEGIN + PERFORM txid_convert_if_recent(txid_current() + (BIGINT '1' << 32)); +EXCEPTION + WHEN invalid_parameter_value THEN + RAISE NOTICE 'got expected xid out of range error'; +END; +$$; + +DO +$$ +BEGIN + PERFORM txid_convert_if_recent((BIGINT '1' << 32) - 1); +EXCEPTION + WHEN invalid_parameter_value THEN + RAISE NOTICE 'got expected xid out of range error'; +END; +$$; + +BEGIN; +CREATE FUNCTION test_future_xid(bigint) +RETURNS void +LANGUAGE plpgsql +AS +$$ +BEGIN + PERFORM txid_convert_if_recent($1); + RAISE EXCEPTION 'didn''t ERROR at xid in the future as expected'; +EXCEPTION + WHEN invalid_parameter_value THEN + RAISE NOTICE 'Got expected error for xid in the future'; +END; +$$; +SELECT test_future_xid(:inprogress + 100000); +ROLLBACK; + BEGIN; CREATE FUNCTION test_future_xid_status(bigint) RETURNS void -- 2.5.5
-- Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-hackers