From 027de6a30dc2684229a12d4deff903048c75de5b Mon Sep 17 00:00:00 2001 From: Craig Ringer 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 | 11 ++++ 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, 184 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"); is transaction ID visible in snapshot? (do not use with subtransaction ids) + txid_convert_if_recent(bigint) + xid + return the 32-bit xid for a 64-bit transaction ID if it isn't wrapped around, otherwise return null + + txid_status(bigint) txid_status report the status of the given xact - committed, aborted, in progress, or NULL if the xid is too old @@ -17210,9 +17215,15 @@ SELECT collation for ('foo' COLLATE "de_DE"); The internal transaction ID 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 epoch counter - so it will not wrap around during the life of an installation. - The data type used by these functions, txid_snapshot, - 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 xid + and must use txid_convert_if_recent(bigint) instead of + casting to xid. + + + + The data type used by the xid snapshot functions, + txid_snapshot, stores information about transaction ID visibility at a particular moment in time. Its components are described in . diff --git a/src/backend/utils/adt/txid.c b/src/backend/utils/adt/txid.c index 4b33e89..258014e 100644 --- a/src/backend/utils/adt/txid.c +++ b/src/backend/utils/adt/txid.c @@ -663,6 +663,17 @@ txid_snapshot_xip(PG_FUNCTION_ARGS) } } +Datum +txid_convert_if_recent(PG_FUNCTION_ARGS) +{ + TransactionId xid; + + if (TransactionIdInRecentPast(PG_GETARG_INT64(0), &xid)) + return TransactionIdGetDatum(xid); + else + PG_RETURN_NULL(); +} + /* * 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