From 7f1c0479a972956fadb6a729c0de34bada4a59f7 Mon Sep 17 00:00:00 2001
From: Craig Ringer <craig@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 14cb067..d3e44dc 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -16970,6 +16970,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_ifrecent(<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>
@@ -16982,9 +16987,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_ifrecent(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 9c3c8da..5a145c2 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_ifrecent(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 8db47d2..ee8ff1c 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -4905,6 +4905,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_ifrecent		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_ifrecent _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..1f54482 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_ifrecent(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_ifrecent(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 df09f62..f65a8d1 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_ifrecent(:committed) = :'committed'::xid;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT txid_convert_ifrecent(:rolledback) = :'rolledback'::xid;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT txid_convert_ifrecent(:inprogress) = :'inprogress'::xid;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT txid_convert_ifrecent(0) = '0'::xid; -- InvalidTransactionId
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT txid_convert_ifrecent(1) = '1'::xid; -- BootstrapTransactionId
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT txid_convert_ifrecent(2) = '2'::xid; -- FrozenTransactionId
+ ?column? 
+----------
+ t
+(1 row)
+
+-- we ignore epoch for the fixed xids
+SELECT txid_convert_ifrecent(BIGINT '1' << 32);
+ txid_convert_ifrecent 
+-----------------------
+                     0
+(1 row)
+
+SELECT txid_convert_ifrecent((BIGINT '1' << 32) + 1);
+ txid_convert_ifrecent 
+-----------------------
+                     1
+(1 row)
+
+SELECT txid_convert_ifrecent((BIGINT '1' << 32) + 2);
+ txid_convert_ifrecent 
+-----------------------
+                     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_ifrecent(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_ifrecent((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_ifrecent($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..a646207 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_ifrecent(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_ifrecent(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 e831323..638ef30 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_ifrecent(:committed) = :'committed'::xid;
+SELECT txid_convert_ifrecent(:rolledback) = :'rolledback'::xid;
+SELECT txid_convert_ifrecent(:inprogress) = :'inprogress'::xid;
+SELECT txid_convert_ifrecent(0) = '0'::xid; -- InvalidTransactionId
+SELECT txid_convert_ifrecent(1) = '1'::xid; -- BootstrapTransactionId
+SELECT txid_convert_ifrecent(2) = '2'::xid; -- FrozenTransactionId
+-- we ignore epoch for the fixed xids
+SELECT txid_convert_ifrecent(BIGINT '1' << 32);
+SELECT txid_convert_ifrecent((BIGINT '1' << 32) + 1);
+SELECT txid_convert_ifrecent((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_ifrecent(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_ifrecent((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_ifrecent($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

