From 71dca408c0030ad76044c6b17367c9fbeac511ec Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Sun, 7 Aug 2016 13:15:55 -0400 Subject: [PATCH] Don't propagate a null subtransaction snapshot up to parent transaction. This oversight could cause logical decoding to fail to decode an outer transaction containing changes, if a subtransaction had an XID but no actual changes. Per bug #14279 from Marko Tiikkaja. Patch by Marko based on analysis by Andrew Gierth. Discussion: <20160804191757.1430.39011@wrigleys.postgresql.org> --- contrib/test_decoding/expected/xact.out | 24 ++++++++++++++++++- contrib/test_decoding/sql/xact.sql | 13 +++++++++- .../replication/logical/reorderbuffer.c | 9 +++---- 3 files changed, 40 insertions(+), 6 deletions(-) diff --git a/contrib/test_decoding/expected/xact.out b/contrib/test_decoding/expected/xact.out index 507b701c3a..ec4745005d 100644 --- a/contrib/test_decoding/expected/xact.out +++ b/contrib/test_decoding/expected/xact.out @@ -6,9 +6,9 @@ SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot', 'test_d init (1 row) --- bug #13844, xids in non-decoded records need to be inspected CREATE TABLE xact_test(data text); INSERT INTO xact_test VALUES ('before-test'); +-- bug #13844, xids in non-decoded records need to be inspected BEGIN; -- perform operation in xact that creates and logs xid, but isn't decoded SELECT * FROM xact_test FOR UPDATE; @@ -33,6 +33,28 @@ SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'inc COMMIT (6 rows) +-- bug #14279, do not propagate null snapshot from subtransaction +BEGIN; +-- first insert +INSERT INTO xact_test VALUES ('main-txn'); +SAVEPOINT foo; +-- now perform operation in subxact that creates and logs xid, but isn't decoded +SELECT 1 FROM xact_test FOR UPDATE LIMIT 1; + ?column? +---------- + 1 +(1 row) + +COMMIT; +-- and now show those changes +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); + data +------------------------------------------------------- + BEGIN + table public.xact_test: INSERT: data[text]:'main-txn' + COMMIT +(3 rows) + DROP TABLE xact_test; SELECT pg_drop_replication_slot('regression_slot'); pg_drop_replication_slot diff --git a/contrib/test_decoding/sql/xact.sql b/contrib/test_decoding/sql/xact.sql index 9ce238f62d..aa555911e8 100644 --- a/contrib/test_decoding/sql/xact.sql +++ b/contrib/test_decoding/sql/xact.sql @@ -3,10 +3,10 @@ SET synchronous_commit = on; SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot', 'test_decoding'); --- bug #13844, xids in non-decoded records need to be inspected CREATE TABLE xact_test(data text); INSERT INTO xact_test VALUES ('before-test'); +-- bug #13844, xids in non-decoded records need to be inspected BEGIN; -- perform operation in xact that creates and logs xid, but isn't decoded SELECT * FROM xact_test FOR UPDATE; @@ -17,6 +17,17 @@ COMMIT; -- and now show those changes SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); +-- bug #14279, do not propagate null snapshot from subtransaction +BEGIN; +-- first insert +INSERT INTO xact_test VALUES ('main-txn'); +SAVEPOINT foo; +-- now perform operation in subxact that creates and logs xid, but isn't decoded +SELECT 1 FROM xact_test FOR UPDATE LIMIT 1; +COMMIT; +-- and now show those changes +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); + DROP TABLE xact_test; SELECT pg_drop_replication_slot('regression_slot'); diff --git a/src/backend/replication/logical/reorderbuffer.c b/src/backend/replication/logical/reorderbuffer.c index 9b5642a516..29bbae601c 100644 --- a/src/backend/replication/logical/reorderbuffer.c +++ b/src/backend/replication/logical/reorderbuffer.c @@ -744,13 +744,14 @@ ReorderBufferCommitChild(ReorderBuffer *rb, TransactionId xid, elog(ERROR, "subxact logged without previous toplevel record"); /* - * Pass the our base snapshot to the parent transaction if it doesn't have + * Pass our base snapshot to the parent transaction if it doesn't have * one, or ours is older. That can happen if there are no changes in the * toplevel transaction but in one of the child transactions. This allows - * the parent to simply use it's base snapshot initially. + * the parent to simply use its base snapshot initially. */ - if (txn->base_snapshot == NULL || - txn->base_snapshot_lsn > subtxn->base_snapshot_lsn) + if (subtxn->base_snapshot != NULL && + (txn->base_snapshot == NULL || + txn->base_snapshot_lsn > subtxn->base_snapshot_lsn)) { txn->base_snapshot = subtxn->base_snapshot; txn->base_snapshot_lsn = subtxn->base_snapshot_lsn;