On 23 August 2016 at 01:03, Robert Haas <robertmh...@gmail.com> wrote:


>
> I think you should use underscores to separate all of the words
> instead of only some of them.
>
>
ifassigned => if_assigned

ifrecent=> if_recent

Updated patch series attached. As before, 0-4 intended for commit, 5 just
because it'll be handy to have around for people doing wraparound related
testing.

Again, thanks for taking a look.

-- 
 Craig Ringer                   http://www.2ndQuadrant.com/
 PostgreSQL Development, 24x7 Support, Training & Services
From 81cbe525261a15a21415af361b3421038eccc895 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/4] 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               | 28 +++++++++++-
 src/backend/access/transam/clog.c    | 23 ----------
 src/backend/catalog/system_views.sql | 20 +++++++++
 src/backend/utils/adt/txid.c         | 82 ++++++++++++++++++++++++++++++++++++
 src/include/access/clog.h            | 23 ++++++++++
 src/include/catalog/pg_proc.h        |  2 +
 src/test/regress/expected/txid.out   | 50 ++++++++++++++++++++++
 src/test/regress/sql/txid.sql        | 35 +++++++++++++++
 8 files changed, 239 insertions(+), 24 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 169a385..8edf490 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -17139,6 +17139,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
@@ -17157,7 +17161,7 @@ SELECT collation for ('foo' COLLATE "de_DE");
       <row>
        <entry><literal><function>txid_current()</function></literal></entry>
        <entry><type>bigint</type></entry>
-       <entry>get current transaction ID, assigning a new one if the current transaction does not have one</entry>
+       <entry>get current 64-bit transaction ID with epoch, assigning a new one if the current transaction does not have one</entry>
       </row>
       <row>
        <entry><literal><function>txid_current_snapshot()</function></literal></entry>
@@ -17184,6 +17188,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 - committed, aborted, in-progress, or null if the xid is too old</entry>
+      </row>
      </tbody>
     </tgroup>
    </table>
@@ -17254,6 +17263,23 @@ SELECT collation for ('foo' COLLATE "de_DE");
    </para>
 
    <para>
+    <function>txid_status(bigint)</> reports the commit status of a recent
+    transaction. Any recent transaction can be identified as one of
+    <itemizedlist>
+     <listitem><para>in-progress</></>
+     <listitem><para>committed</></>
+     <listitem><para>aborted</></>
+    </itemizedlist>
+    Prepared transactions are identified as <literal>in-progress</>.
+    The commit status of transactions older than the transaction ID wrap-around
+    threshold is no longer known by the system, so <function>txid_status</>
+    returns <literal>NULL</> for such transactions. Applications may use
+    <function>txid_status</> 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.
+   </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/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index ada2142..e173da2 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1006,6 +1006,26 @@ LANGUAGE INTERNAL
 STRICT IMMUTABLE PARALLEL SAFE
 AS 'jsonb_insert';
 
+CREATE TYPE txid_status AS ENUM ('committed', 'in-progress', 'aborted');
+
+CREATE FUNCTION
+  txid_status(txid bigint)
+RETURNS txid_status
+LANGUAGE sql
+VOLATILE PARALLEL SAFE
+AS $$
+SELECT CASE
+  WHEN s IS NULL THEN NULL::txid_status
+  WHEN s = -1 THEN 'aborted'::txid_status
+  WHEN s = 0 THEN 'in-progress'::txid_status
+  WHEN s = 1 THEN 'committed'::txid_status
+END
+FROM pg_catalog.txid_status_internal($1) s;
+$$;
+
+COMMENT ON FUNCTION txid_status(bigint)
+IS 'get commit status of given recent xid or null if too old';
+
 -- The default permissions for functions mean that anyone can execute them.
 -- A number of functions shouldn't be executable by just anyone, but rather
 -- than use explicit 'superuser()' checks in those functions, we use the GRANT
diff --git a/src/backend/utils/adt/txid.c b/src/backend/utils/adt/txid.c
index c2069a9..0480c99 100644
--- a/src/backend/utils/adt/txid.c
+++ b/src/backend/utils/adt/txid.c
@@ -21,9 +21,11 @@
 
 #include "postgres.h"
 
+#include "access/clog.h"
 #include "access/transam.h"
 #include "access/xact.h"
 #include "access/xlog.h"
+#include "catalog/pg_proc.h"
 #include "funcapi.h"
 #include "miscadmin.h"
 #include "libpq/pqformat.h"
@@ -117,6 +119,50 @@ 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 wraparound param to true
+ * if wraparound is detected, false otherwise.
+ */
+static TransactionId
+get_xid_in_recent_past(txid xid_with_epoch, bool *wraparound)
+{
+	uint32 xid_epoch = (uint32)(xid_with_epoch >>32);
+	TransactionId xid = (TransactionId)(xid_with_epoch);
+	TxidEpoch now_epoch;
+
+	load_xid_epoch(&now_epoch);
+
+	*wraparound = 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 too far in the past */
+		*wraparound = true;
+	}
+	else
+	{
+		Assert(TransactionIdPrecedesOrEquals(xid, now_epoch.last_xid));
+	}
+
+	return xid;
+}
+
+/*
  * txid comparator for qsort/bsearch
  */
 static int
@@ -354,6 +400,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)
@@ -637,3 +686,36 @@ txid_snapshot_xip(PG_FUNCTION_ARGS)
 		SRF_RETURN_DONE(fctx);
 	}
 }
+
+/*
+ * Underlying implementation of txid_status, which is mapped to an enum in
+ * system_views.sql.
+ */
+Datum
+txid_status_internal(PG_FUNCTION_ARGS)
+{
+	bool wraparound;
+	uint64 xid_with_epoch = PG_GETARG_INT64(0);
+	TransactionId xid = get_xid_in_recent_past(xid_with_epoch, &wraparound);
+
+	if (!TransactionIdIsValid(xid))
+	{
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("transaction ID "UINT64_FORMAT" is an invalid xid",
+					xid_with_epoch)));
+	}
+
+	if (wraparound)
+		PG_RETURN_NULL();
+	else if (TransactionIdIsCurrentTransactionId(xid) || TransactionIdIsInProgress(xid))
+		PG_RETURN_INT32(0);
+	else if (TransactionIdDidCommit(xid))
+		PG_RETURN_INT32(1);
+	else if (TransactionIdDidAbort(xid))
+		PG_RETURN_INT32(-1);
+	else
+		/* shouldn't happen */
+		ereport(ERROR,
+			(errmsg_internal("unable to determine commit status of xid "UINT64_FORMAT, xid)));
+}
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 6fed7a0..d99384c 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -4914,6 +4914,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_internal		PGNSP PGUID 12 1  0 0 0 f f f f t f v s 1 0 23 "20" _null_ _null_ _null_ _null_ _null_ txid_status_internal _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/test/regress/expected/txid.out b/src/test/regress/expected/txid.out
index ddd217e..61299c5 100644
--- a/src/test/regress/expected/txid.out
+++ b/src/test/regress/expected/txid.out
@@ -238,3 +238,53 @@ SELECT txid_snapshot '1:9223372036854775808:3';
 ERROR:  invalid input syntax for type txid_snapshot: "1:9223372036854775808:3"
 LINE 1: SELECT txid_snapshot '1:9223372036854775808:3';
                              ^
+-- 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)
+
+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 b6650b9..0bfdfe2 100644
--- a/src/test/regress/sql/txid.sql
+++ b/src/test/regress/sql/txid.sql
@@ -52,3 +52,38 @@ select txid_visible_in_snapshot('1000100010001015', '1000100010001000:1000100010
 -- test 64bit overflow
 SELECT txid_snapshot '1:9223372036854775807:3';
 SELECT txid_snapshot '1:9223372036854775808:3';
+
+-- 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;
+
+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 8a4d038e354e98c1e5a5fd10ff8822a50ad1b4ae Mon Sep 17 00:00:00 2001
From: Craig Ringer <cr...@2ndquadrant.com>
Date: Fri, 19 Aug 2016 14:46:53 +0800
Subject: [PATCH 2/4] Add txid_current_ifassigned()

Add a variant of txid_current() that returns null if no xid is assigned
instead of assigning one or, on a replica, ERRORing.

Per suggestion from Jim Nasby
---
 doc/src/sgml/func.sgml             |  9 +++++++++
 src/backend/utils/adt/txid.c       | 21 +++++++++++++++++++++
 src/include/catalog/pg_proc.h      |  2 ++
 src/test/regress/expected/txid.out | 15 +++++++++++++++
 src/test/regress/sql/txid.sql      |  6 ++++++
 5 files changed, 53 insertions(+)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 8edf490..9434e9b 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -17120,6 +17120,10 @@ SELECT collation for ('foo' COLLATE "de_DE");
    </indexterm>
 
    <indexterm>
+    <primary>txid_current_if_assigned</primary>
+   </indexterm>
+
+   <indexterm>
     <primary>txid_current_snapshot</primary>
    </indexterm>
 
@@ -17164,6 +17168,11 @@ SELECT collation for ('foo' COLLATE "de_DE");
        <entry>get current 64-bit transaction ID with epoch, assigning a new one if the current transaction does not have one</entry>
       </row>
       <row>
+       <entry><literal><function>txid_current_if_assigned()</function></literal></entry>
+       <entry><type>bigint</type></entry>
+       <entry>same as <function>txid_current()</function> but returns null instead of assigning an xid if none is already assigned</entry>
+      </row>
+      <row>
        <entry><literal><function>txid_current_snapshot()</function></literal></entry>
        <entry><type>txid_snapshot</type></entry>
        <entry>get current snapshot</entry>
diff --git a/src/backend/utils/adt/txid.c b/src/backend/utils/adt/txid.c
index 0480c99..05b0c96 100644
--- a/src/backend/utils/adt/txid.c
+++ b/src/backend/utils/adt/txid.c
@@ -426,6 +426,27 @@ txid_current(PG_FUNCTION_ARGS)
 }
 
 /*
+ * Same as txid_current() but doesn't assign a new xid if there isn't one
+ * yet.
+ */
+Datum
+txid_current_if_assigned(PG_FUNCTION_ARGS)
+{
+	txid		val;
+	TxidEpoch	state;
+	TransactionId	topxid = GetTopTransactionIdIfAny();
+
+	if (topxid == InvalidTransactionId)
+		PG_RETURN_NULL();
+
+	load_xid_epoch(&state);
+
+	val = convert_xid(topxid, &state);
+
+	PG_RETURN_INT64(val);
+}
+
+/*
  * txid_current_snapshot() returns txid_snapshot
  *
  *		Return current snapshot in TXID format
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index d99384c..80420ef 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -4904,6 +4904,8 @@ DATA(insert OID = 2942 (  txid_snapshot_send		PGNSP PGUID 12 1  0 0 0 f f f f t
 DESCR("I/O");
 DATA(insert OID = 2943 (  txid_current				PGNSP PGUID 12 1  0 0 0 f f f f t f s u 0 0 20 "" _null_ _null_ _null_ _null_ _null_ txid_current _null_ _null_ _null_ ));
 DESCR("get current transaction ID");
+DATA(insert OID = 3348 (  txid_current_if_assigned	PGNSP PGUID 12 1  0 0 0 f f f f t f s u 0 0 20 "" _null_ _null_ _null_ _null_ _null_ txid_current_if_assigned _null_ _null_ _null_ ));
+DESCR("get current transaction ID");
 DATA(insert OID = 2944 (  txid_current_snapshot		PGNSP PGUID 12 1  0 0 0 f f f f t f s s 0 0 2970 "" _null_ _null_ _null_ _null_ _null_ txid_current_snapshot _null_ _null_ _null_ ));
 DESCR("get current snapshot");
 DATA(insert OID = 2945 (  txid_snapshot_xmin		PGNSP PGUID 12 1  0 0 0 f f f f t f i s 1 0 20 "2970" _null_ _null_ _null_ _null_ _null_ txid_snapshot_xmin _null_ _null_ _null_ ));
diff --git a/src/test/regress/expected/txid.out b/src/test/regress/expected/txid.out
index 61299c5..9450d5e 100644
--- a/src/test/regress/expected/txid.out
+++ b/src/test/regress/expected/txid.out
@@ -238,6 +238,21 @@ SELECT txid_snapshot '1:9223372036854775808:3';
 ERROR:  invalid input syntax for type txid_snapshot: "1:9223372036854775808:3"
 LINE 1: SELECT txid_snapshot '1:9223372036854775808:3';
                              ^
+BEGIN;
+SELECT txid_current_if_assigned() IS NULL;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT txid_current() \gset
+SELECT txid_current_if_assigned() IS NOT DISTINCT FROM BIGINT :'txid_current';
+ ?column? 
+----------
+ t
+(1 row)
+
+COMMIT;
 -- test xid status functions
 BEGIN;
 SELECT txid_current() AS committed \gset
diff --git a/src/test/regress/sql/txid.sql b/src/test/regress/sql/txid.sql
index 0bfdfe2..70e18e3 100644
--- a/src/test/regress/sql/txid.sql
+++ b/src/test/regress/sql/txid.sql
@@ -53,6 +53,12 @@ select txid_visible_in_snapshot('1000100010001015', '1000100010001000:1000100010
 SELECT txid_snapshot '1:9223372036854775807:3';
 SELECT txid_snapshot '1:9223372036854775808:3';
 
+BEGIN;
+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
-- 
2.5.5

From f6414c2fcb73800d7320b4673c3976bf67d93d4c Mon Sep 17 00:00:00 2001
From: Craig Ringer <cr...@2ndquadrant.com>
Date: Fri, 19 Aug 2016 14:49:52 +0800
Subject: [PATCH 3/4] Add txid_convert_ifrecent() 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             |   2 +
 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 +++++++++++++++
 7 files changed, 183 insertions(+), 7 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 9434e9b..8ddf652 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 - committed, aborted, in-progress, 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 05b0c96..ce25093 100644
--- a/src/backend/utils/adt/txid.c
+++ b/src/backend/utils/adt/txid.c
@@ -708,6 +708,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);
+}
+
 /*
  * Underlying implementation of txid_status, which is mapped to an enum in
  * system_views.sql.
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 80420ef..226bed7 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -4916,6 +4916,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 = 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");
 DATA(insert OID = 3346 (  txid_status_internal		PGNSP PGUID 12 1  0 0 0 f f f f t f v s 1 0 23 "20" _null_ _null_ _null_ _null_ _null_ txid_status_internal _null_ _null_ _null_ ));
 DESCR("commit status of transaction");
 
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 9450d5e..ed038b4 100644
--- a/src/test/regress/expected/txid.out
+++ b/src/test/regress/expected/txid.out
@@ -262,6 +262,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 
 -----------
@@ -281,6 +338,49 @@ SELECT txid_status(:inprogress) AS inprogress;
 (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 70e18e3..53505bc 100644
--- a/src/test/regress/sql/txid.sql
+++ b/src/test/regress/sql/txid.sql
@@ -71,12 +71,63 @@ 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;
 
 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

From bd540abf3e31a4f5623449ba0669e61722bf7647 Mon Sep 17 00:00:00 2001
From: Craig Ringer <cr...@2ndquadrant.com>
Date: Fri, 19 Aug 2016 14:57:54 +0800
Subject: [PATCH 4/4] Add txid_incinerate() test function for fast wrap-around

Burn txids, really, really, really, really fast. Especially if fsync
is off.

Good for reaching wraparound conditions quickly.
---
 src/backend/utils/adt/txid.c             | 170 +++++++++++++++++++++++++++++++
 src/include/catalog/pg_proc.h            |   2 +
 src/test/regress/expected/wraparound.out | 161 +++++++++++++++++++++++++++++
 src/test/regress/parallel_schedule       |   3 +
 src/test/regress/serial_schedule         |   1 +
 src/test/regress/sql/wraparound.sql      | 118 +++++++++++++++++++++
 6 files changed, 455 insertions(+)
 create mode 100644 src/test/regress/expected/wraparound.out
 create mode 100644 src/test/regress/sql/wraparound.sql

diff --git a/src/backend/utils/adt/txid.c b/src/backend/utils/adt/txid.c
index ce25093..1137ddb 100644
--- a/src/backend/utils/adt/txid.c
+++ b/src/backend/utils/adt/txid.c
@@ -752,3 +752,173 @@ txid_status_internal(PG_FUNCTION_ARGS)
 		ereport(ERROR,
 			(errmsg_internal("unable to determine commit status of xid "UINT64_FORMAT, xid)));
 }
+
+/*
+ * Internal function for test only use to burn transaction IDs
+ * as fast as possible.
+ *
+ * Forcibly advance to just before wraparound.
+ *
+ * This will cause commit/rollback of our own xact to fail because
+ * the clog page has been truncated away.
+ *
+ * No safety check is performed to ensure nothing else has an xid.
+ * They'll fail on commit. Should really lock procarray.
+ *
+ * There's also no attempt to keep datfrozenxid correct for the other
+ * DBs. The user gets the fun of freezing them.
+ */
+Datum
+txid_incinerate(PG_FUNCTION_ARGS)
+{
+	const char *target;
+	int nreserved;
+	int i;
+
+	TransactionId lastAllocatedXid;
+	TransactionId clogPageFirstXid;
+	TransactionId targetNextXid;
+
+	if (!superuser())
+		elog(ERROR, "txid_incinerate may only be called by the superuser");
+
+	if (PG_ARGISNULL(0))
+		elog(ERROR, "xid argument must be non-null");
+
+	if (PG_ARGISNULL(1))
+	{
+		nreserved = 0;
+	}
+	else
+	{
+		nreserved = PG_GETARG_INT32(1);
+		if (nreserved < 0)
+			elog(ERROR, "nreserved xids must be >= 0");
+	}
+
+	target = text_to_cstring(PG_GETARG_TEXT_PP(0));
+
+	LWLockAcquire(XidGenLock, LW_SHARED);
+	if (GetTopTransactionIdIfAny() != InvalidTransactionId)
+	{
+		LWLockRelease(XidGenLock);
+		ereport(ERROR,
+				(errmsg_internal("can't burn XIDs in a session with an xid allocated")));
+	}
+
+	lastAllocatedXid = ShmemVariableCache->nextXid;
+	TransactionIdRetreat(lastAllocatedXid);
+
+	if (strcmp(target, "stop"))
+		targetNextXid = ShmemVariableCache->xidStopLimit;
+	else if (strcmp(target, "warn"))
+		targetNextXid = ShmemVariableCache->xidWarnLimit;
+	else if (strcmp(target, "vac"))
+		targetNextXid = ShmemVariableCache->xidVacLimit;
+	else if (strcmp(target, "wrap"))
+		targetNextXid = ShmemVariableCache->xidWrapLimit;
+	else if (strcmp(target, "page"))
+		targetNextXid = ShmemVariableCache->nextXid + CLOG_XACTS_PER_PAGE - nreserved;
+	else
+	{
+		unsigned long parsed;
+		char *endp;
+		parsed = strtol(target, &endp, 10);
+		if (*endp != '\0')
+			elog(ERROR, "Argument must be an xid or one of the strings page, stop, warn, vac or wrap");
+		if (!TransactionIdIsNormal((TransactionId)parsed))
+			elog(ERROR, "Argument xid must be a normal xid, not the invalid/frozen/bootstrap xid");
+		targetNextXid = (TransactionId)parsed;
+	}
+
+	for (i = 0; i < nreserved; i++)
+		TransactionIdRetreat(targetNextXid);
+
+	if (!TransactionIdFollowsOrEquals(targetNextXid, ShmemVariableCache->nextXid))
+		elog(ERROR, "Target xid %u is <= current xid %u in modulo 32",
+			  targetNextXid, ShmemVariableCache->nextXid);
+
+	elog(NOTICE, "xid limits are: vac=%u, warn=%u, stop=%u, wrap=%u, oldest=%u, next=%u; target xid is %u",
+		ShmemVariableCache->xidVacLimit,
+		ShmemVariableCache->xidWarnLimit,
+		ShmemVariableCache->xidStopLimit,
+		ShmemVariableCache->xidWrapLimit,
+		ShmemVariableCache->nextXid,
+		ShmemVariableCache->oldestXid,
+		targetNextXid);
+
+	Assert(TransactionIdPrecedes(ShmemVariableCache->nextXid, ShmemVariableCache->xidStopLimit));
+	Assert(TransactionIdPrecedesOrEquals(ShmemVariableCache->nextXid, ShmemVariableCache->xidWrapLimit));
+
+	/* Advance nextXid to the last xid on the current clog page */
+	clogPageFirstXid = ShmemVariableCache->nextXid - TransactionIdToPgIndex(ShmemVariableCache->nextXid);
+	ShmemVariableCache->nextXid = clogPageFirstXid + (CLOG_XACTS_PER_PAGE - 1);
+	elog(DEBUG1, "txid_incinerate: Advanced xid to %u, first %u on page %u",
+			ShmemVariableCache->nextXid, clogPageFirstXid,
+			TransactionIdToPage(ShmemVariableCache->nextXid));
+		
+	/*
+	 * Write new clog pages and advance to the end of the next page, until
+	 * we've allocated the last clog page. This might take a while.
+	 *
+	 * At each step, force the next xid forward and extend the clog. We must
+	 * allocate the first xid on the last page so that ExtendCLOG actually does
+	 * some work, since otherwise it just shortcuts out.
+	 */
+	do
+	{
+		if (clogPageFirstXid == FirstNormalTransactionId)
+			clogPageFirstXid = CLOG_XACTS_PER_PAGE;
+		else
+			clogPageFirstXid += CLOG_XACTS_PER_PAGE;
+
+		elog(DEBUG1, "txid_incinerate: nextXid %u", ShmemVariableCache->nextXid);
+
+		if (TransactionIdPrecedes(clogPageFirstXid, targetNextXid)
+			&& TransactionIdPrecedesOrEquals(targetNextXid, clogPageFirstXid + (CLOG_XACTS_PER_PAGE - 1)))
+		{
+			ShmemVariableCache->nextXid = targetNextXid;
+			elog(DEBUG1, "txid_incinerate: reached target xid, next page %u greater than target %u",
+				targetNextXid, targetNextXid + CLOG_XACTS_PER_PAGE);
+		}
+		else
+		{
+			ShmemVariableCache->nextXid = clogPageFirstXid + (CLOG_XACTS_PER_PAGE - 1);
+		}
+
+		if (clogPageFirstXid < FirstNormalTransactionId)
+		{
+			clogPageFirstXid = FirstNormalTransactionId;
+		}
+
+		Assert(TransactionIdToPgIndex(clogPageFirstXid) == 0 || clogPageFirstXid == FirstNormalTransactionId);
+
+		Assert(TransactionIdPrecedesOrEquals(ShmemVariableCache->nextXid, ShmemVariableCache->xidWrapLimit));
+
+		ExtendCLOG(clogPageFirstXid);
+
+		CHECK_FOR_INTERRUPTS();
+	}
+	while (TransactionIdToPage(ShmemVariableCache->nextXid) != (targetNextXid/CLOG_XACTS_PER_PAGE));
+
+	elog(DEBUG1, "txid_incinerate: done extending clog and advancing counter, nextXid is %u",
+		 ShmemVariableCache->nextXid);
+
+	Assert(TransactionIdPrecedesOrEquals(ShmemVariableCache->nextXid, ShmemVariableCache->xidWrapLimit));
+	
+	/*
+	 * We'd really like to totally reset the clog by truncating it and
+	 * moving the wraparound pointer, but we can't do that unless all DBs
+	 * are already frozen.
+	 *
+	 * We can't freeze here since we can't access other DBs. So we've got
+	 * to let the user do the job.
+	 */
+
+	elog(NOTICE, "txid_incinerate: advanced nextXid to %u",
+		ShmemVariableCache->nextXid);
+
+	LWLockRelease(XidGenLock);
+
+	PG_RETURN_VOID();
+}
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 226bed7..fde95bf 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -4920,6 +4920,8 @@ DATA(insert OID = 3344 (  txid_convert_if_recent		PGNSP PGUID 12 1  0 0 0 f f f
 DESCR("get the xid from a bigint transaction id if not wrapped around");
 DATA(insert OID = 3346 (  txid_status_internal		PGNSP PGUID 12 1  0 0 0 f f f f t f v s 1 0 23 "20" _null_ _null_ _null_ _null_ _null_ txid_status_internal _null_ _null_ _null_ ));
 DESCR("commit status of transaction");
+DATA(insert OID = 3347 (  txid_incinerate			PGNSP PGUID 12 1  0 0 0 f f f f t f v s 2 0 2278 "25 23" _null_ _null_ _null_ _null_ _null_ txid_incinerate _null_ _null_ _null_ ));
+DESCR("burn xids fast");
 
 /* 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/test/regress/expected/wraparound.out b/src/test/regress/expected/wraparound.out
new file mode 100644
index 0000000..5638dd6
--- /dev/null
+++ b/src/test/regress/expected/wraparound.out
@@ -0,0 +1,161 @@
+-- We need to be able to force vacuuming of template0
+UPDATE pg_database
+SET datallowconn = true
+WHERE datname = 'template0';
+-- For debugging these tests you'll find something like this useful:
+-- SELECT txid_current() \gset
+-- SELECT BIGINT :'txid_current' AS txid,
+--       BIGINT :'txid_current' >> 32 AS epoch,
+--       BIGINT :'txid_current' & 4294967295 AS xid32;
+SELECT txid_current() AS before_wrap_xid \gset
+SET client_min_messages = 'error';
+SELECT txid_incinerate('stop', 1000);
+ txid_incinerate 
+-----------------
+ 
+(1 row)
+
+-- Should be near UINT32_MAX/2 now
+SELECT txid_current() \gset
+SELECT BIGINT :'txid_current' > (BIGINT '1' << 31) - (BIGINT '1' << 30);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT BIGINT :'txid_current' < (BIGINT '1' << 31) + (BIGINT '1' << 30);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT BIGINT :'txid_current' >> 32 AS txid_current_epoch;
+ txid_current_epoch 
+--------------------
+                  0
+(1 row)
+
+\c template0
+SET client_min_messages = 'error';
+VACUUM FREEZE;
+\c template1
+SET client_min_messages = 'error';
+VACUUM FREEZE;
+\c postgres
+SET client_min_messages = 'error';
+VACUUM FREEZE;
+\c regression
+SET client_min_messages = 'error';
+VACUUM FREEZE;
+CHECKPOINT;
+select min(datfrozenxid::text::bigint) AS min_datfrozenxid from pg_database \gset
+SELECT BIGINT :'min_datfrozenxid' > (BIGINT '1' << 31) - (BIGINT '1' << 30);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT BIGINT :'min_datfrozenxid' < (BIGINT '1' << 31) + (BIGINT '1' << 30);
+ ?column? 
+----------
+ t
+(1 row)
+
+-- That got us to nearly UINT32_MAX/2, another run will get us to near UINT32_MAX
+SET client_min_messages = 'error';
+SELECT txid_incinerate('stop', 1000);
+ txid_incinerate 
+-----------------
+ 
+(1 row)
+
+SELECT txid_current() \gset
+SELECT BIGINT :'txid_current' > (BIGINT '1' << 31) + (BIGINT '1' << 30);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT BIGINT :'txid_current' < (BIGINT '1' << 32);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT BIGINT :'txid_current' >> 32 AS txid_current_epoch;
+ txid_current_epoch 
+--------------------
+                  0
+(1 row)
+
+\c template0
+SET client_min_messages = 'error';
+VACUUM FREEZE;
+\c template1
+SET client_min_messages = 'error';
+VACUUM FREEZE;
+\c postgres
+SET client_min_messages = 'error';
+VACUUM FREEZE;
+\c regression
+SET client_min_messages = 'error';
+VACUUM FREEZE;
+CHECKPOINT;
+select min(datfrozenxid::text::bigint) AS min_datfrozenxid from pg_database \gset
+SELECT BIGINT :'min_datfrozenxid' > (BIGINT '1' << 31) + (BIGINT '1' << 30);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT BIGINT :'min_datfrozenxid' < (BIGINT '1' << 32);
+ ?column? 
+----------
+ t
+(1 row)
+
+-- We should be near UINT32_MAX now, so the next run will
+-- bring us across the epoch boundary.
+SET client_min_messages = 'error';
+SELECT txid_incinerate('stop', 1000);
+ txid_incinerate 
+-----------------
+ 
+(1 row)
+
+SELECT txid_current() \gset
+SELECT BIGINT :'txid_current' > (BIGINT '1' << 32);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT BIGINT :'txid_current' >> 32 AS txid_current_epoch;
+ txid_current_epoch 
+--------------------
+                  1
+(1 row)
+
+\c template0
+SET client_min_messages = 'error';
+VACUUM FREEZE;
+\c template1
+SET client_min_messages = 'error';
+VACUUM FREEZE;
+\c postgres
+SET client_min_messages = 'error';
+VACUUM FREEZE;
+CHECKPOINT;
+\c regression
+SET client_min_messages = 'error';
+VACUUM FREEZE;
+UPDATE pg_database
+SET datallowconn = false
+WHERE datname = 'template0';
+-- Make sure our txid functions handle the epoch wrap
+SELECT txid_convert_if_recent(BIGINT :'before_wrap_xid');
+ txid_convert_if_recent 
+------------------------
+                       
+(1 row)
+
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 1cb5dfc..e5c34d0 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -42,6 +42,9 @@ test: create_type
 test: create_table
 test: create_function_2
 
+# Force txid wraparound
+test: wraparound
+
 # ----------
 # Load huge amounts of data
 # We should split the data files into single files and then
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 8958d8c..1fa4852 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -55,6 +55,7 @@ test: create_function_1
 test: create_type
 test: create_table
 test: create_function_2
+test: wraparound
 test: copy
 test: copyselect
 test: copydml
diff --git a/src/test/regress/sql/wraparound.sql b/src/test/regress/sql/wraparound.sql
new file mode 100644
index 0000000..f67c435
--- /dev/null
+++ b/src/test/regress/sql/wraparound.sql
@@ -0,0 +1,118 @@
+-- We need to be able to force vacuuming of template0
+UPDATE pg_database
+SET datallowconn = true
+WHERE datname = 'template0';
+
+-- For debugging these tests you'll find something like this useful:
+-- SELECT txid_current() \gset
+-- SELECT BIGINT :'txid_current' AS txid,
+--       BIGINT :'txid_current' >> 32 AS epoch,
+--       BIGINT :'txid_current' & 4294967295 AS xid32;
+
+SELECT txid_current() AS before_wrap_xid \gset
+
+
+SET client_min_messages = 'error';
+SELECT txid_incinerate('stop', 1000);
+
+-- Should be near UINT32_MAX/2 now
+SELECT txid_current() \gset
+SELECT BIGINT :'txid_current' > (BIGINT '1' << 31) - (BIGINT '1' << 30);
+SELECT BIGINT :'txid_current' < (BIGINT '1' << 31) + (BIGINT '1' << 30);
+SELECT BIGINT :'txid_current' >> 32 AS txid_current_epoch;
+
+\c template0
+SET client_min_messages = 'error';
+VACUUM FREEZE;
+
+\c template1
+SET client_min_messages = 'error';
+VACUUM FREEZE;
+
+\c postgres
+SET client_min_messages = 'error';
+VACUUM FREEZE;
+
+\c regression
+SET client_min_messages = 'error';
+VACUUM FREEZE;
+
+CHECKPOINT;
+
+select min(datfrozenxid::text::bigint) AS min_datfrozenxid from pg_database \gset
+SELECT BIGINT :'min_datfrozenxid' > (BIGINT '1' << 31) - (BIGINT '1' << 30);
+SELECT BIGINT :'min_datfrozenxid' < (BIGINT '1' << 31) + (BIGINT '1' << 30);
+
+
+
+
+
+
+-- That got us to nearly UINT32_MAX/2, another run will get us to near UINT32_MAX
+SET client_min_messages = 'error';
+SELECT txid_incinerate('stop', 1000);
+SELECT txid_current() \gset
+SELECT BIGINT :'txid_current' > (BIGINT '1' << 31) + (BIGINT '1' << 30);
+SELECT BIGINT :'txid_current' < (BIGINT '1' << 32);
+SELECT BIGINT :'txid_current' >> 32 AS txid_current_epoch;
+
+\c template0
+SET client_min_messages = 'error';
+VACUUM FREEZE;
+
+\c template1
+SET client_min_messages = 'error';
+VACUUM FREEZE;
+
+\c postgres
+SET client_min_messages = 'error';
+VACUUM FREEZE;
+
+\c regression
+SET client_min_messages = 'error';
+VACUUM FREEZE;
+
+CHECKPOINT;
+
+select min(datfrozenxid::text::bigint) AS min_datfrozenxid from pg_database \gset
+SELECT BIGINT :'min_datfrozenxid' > (BIGINT '1' << 31) + (BIGINT '1' << 30);
+SELECT BIGINT :'min_datfrozenxid' < (BIGINT '1' << 32);
+
+
+
+
+-- We should be near UINT32_MAX now, so the next run will
+-- bring us across the epoch boundary.
+SET client_min_messages = 'error';
+SELECT txid_incinerate('stop', 1000);
+SELECT txid_current() \gset
+SELECT BIGINT :'txid_current' > (BIGINT '1' << 32);
+SELECT BIGINT :'txid_current' >> 32 AS txid_current_epoch;
+
+\c template0
+SET client_min_messages = 'error';
+VACUUM FREEZE;
+
+\c template1
+SET client_min_messages = 'error';
+VACUUM FREEZE;
+
+\c postgres
+SET client_min_messages = 'error';
+VACUUM FREEZE;
+
+CHECKPOINT;
+
+\c regression
+SET client_min_messages = 'error';
+VACUUM FREEZE;
+
+
+UPDATE pg_database
+SET datallowconn = false
+WHERE datname = 'template0';
+
+
+
+-- Make sure our txid functions handle the epoch wrap
+SELECT txid_convert_if_recent(BIGINT :'before_wrap_xid');
-- 
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

Reply via email to