From 3218679574539d53e51c0c983507a57eb0c66898 Mon Sep 17 00:00:00 2001 From: Masahiko Sawada Date: Thu, 26 Mar 2020 21:41:29 +0500 Subject: [PATCH v23 6/7] Add regression tests for foreign twophase commit. Co-authored-by: Masahiko Sawada, Ashutosh Bapat --- src/test/modules/Makefile | 1 + src/test/modules/test_fdwxact/.gitignore | 4 + src/test/modules/test_fdwxact/Makefile | 28 ++ .../test_fdwxact/expected/test_fdwxact.out | 223 +++++++++ src/test/modules/test_fdwxact/fdwxact.conf | 7 + .../modules/test_fdwxact/sql/test_fdwxact.sql | 193 +++++++ src/test/modules/test_fdwxact/t/001_basic.pl | 137 +++++ .../test_fdwxact/test_fdwxact--1.0.sql | 44 ++ src/test/modules/test_fdwxact/test_fdwxact.c | 471 ++++++++++++++++++ .../modules/test_fdwxact/test_fdwxact.control | 4 + src/test/recovery/Makefile | 2 +- src/test/recovery/t/021_fdwxact.pl | 175 +++++++ src/test/regress/pg_regress.c | 13 +- 13 files changed, 1297 insertions(+), 5 deletions(-) create mode 100644 src/test/modules/test_fdwxact/.gitignore create mode 100644 src/test/modules/test_fdwxact/Makefile create mode 100644 src/test/modules/test_fdwxact/expected/test_fdwxact.out create mode 100644 src/test/modules/test_fdwxact/fdwxact.conf create mode 100644 src/test/modules/test_fdwxact/sql/test_fdwxact.sql create mode 100644 src/test/modules/test_fdwxact/t/001_basic.pl create mode 100644 src/test/modules/test_fdwxact/test_fdwxact--1.0.sql create mode 100644 src/test/modules/test_fdwxact/test_fdwxact.c create mode 100644 src/test/modules/test_fdwxact/test_fdwxact.control create mode 100644 src/test/recovery/t/021_fdwxact.pl diff --git a/src/test/modules/Makefile b/src/test/modules/Makefile index 29de73c060..8a48e6ba19 100644 --- a/src/test/modules/Makefile +++ b/src/test/modules/Makefile @@ -13,6 +13,7 @@ SUBDIRS = \ test_bloomfilter \ test_ddl_deparse \ test_extensions \ + test_fdwxact \ test_ginpostinglist \ test_integerset \ test_misc \ diff --git a/src/test/modules/test_fdwxact/.gitignore b/src/test/modules/test_fdwxact/.gitignore new file mode 100644 index 0000000000..5dcb3ff972 --- /dev/null +++ b/src/test/modules/test_fdwxact/.gitignore @@ -0,0 +1,4 @@ +# Generated subdirectories +/log/ +/results/ +/tmp_check/ diff --git a/src/test/modules/test_fdwxact/Makefile b/src/test/modules/test_fdwxact/Makefile new file mode 100644 index 0000000000..b3fc99aee3 --- /dev/null +++ b/src/test/modules/test_fdwxact/Makefile @@ -0,0 +1,28 @@ +# src/test/modules/test_fdwxact/Makefile + +MODULE_big = test_fdwxact +OBJS = \ + $(WIN32RES) \ + test_fdwxact.o +PGFILEDESC = "test_fdwxact - test code for src/backend/access/fdwxact" + +EXTENSION = test_fdwxact +DATA = test_fdwxact--1.0.sql + +REGRESS_OPTS = --temp-config=$(top_srcdir)/src/test/modules/test_fdwxact/fdwxact.conf +REGRESS = test_fdwxact + +NO_INSTALLCHECK = 1 + +TAP_TESTS =1 + +ifdef USE_PGXS +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +else +subdir = src/test/modules/test_fdwxact +top_builddir = ../../../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif diff --git a/src/test/modules/test_fdwxact/expected/test_fdwxact.out b/src/test/modules/test_fdwxact/expected/test_fdwxact.out new file mode 100644 index 0000000000..c6a91ac9f1 --- /dev/null +++ b/src/test/modules/test_fdwxact/expected/test_fdwxact.out @@ -0,0 +1,223 @@ +-- +-- Test for foreign transaction management. +-- +CREATE EXTENSION test_fdwxact; +-- setup two servers that don't support transaction management API +CREATE SERVER srv_1 FOREIGN DATA WRAPPER test_fdw; +CREATE SERVER srv_2 FOREIGN DATA WRAPPER test_fdw; +-- setup two servers that support only commit and rollback API +CREATE SERVER srv_no2pc_1 FOREIGN DATA WRAPPER test_no2pc_fdw; +CREATE SERVER srv_no2pc_2 FOREIGN DATA WRAPPER test_no2pc_fdw; +-- setup two servers that support commit, rollback and prepare API. +-- That is, those two server support two-phase commit protocol. +CREATE SERVER srv_2pc_1 FOREIGN DATA WRAPPER test_2pc_fdw; +CREATE SERVER srv_2pc_2 FOREIGN DATA WRAPPER test_2pc_fdw; +CREATE TABLE t (i int); +CREATE FOREIGN TABLE ft_1 (i int) SERVER srv_1; +CREATE FOREIGN TABLE ft_2 (i int) SERVER srv_2; +CREATE FOREIGN TABLE ft_no2pc_1 (i int) SERVER srv_no2pc_1; +CREATE FOREIGN TABLE ft_no2pc_2 (i int) SERVER srv_no2pc_2; +CREATE FOREIGN TABLE ft_2pc_1 (i int) SERVER srv_2pc_1; +CREATE FOREIGN TABLE ft_2pc_2 (i int) SERVER srv_2pc_2; +CREATE USER MAPPING FOR PUBLIC SERVER srv_1; +CREATE USER MAPPING FOR PUBLIC SERVER srv_2; +CREATE USER MAPPING FOR PUBLIC SERVER srv_no2pc_1; +CREATE USER MAPPING FOR PUBLIC SERVER srv_no2pc_2; +CREATE USER MAPPING FOR PUBLIC SERVER srv_2pc_1; +CREATE USER MAPPING FOR PUBLIC SERVER srv_2pc_2; +-- Test 'disabled' mode. +-- Modifies one or two servers but since we don't require two-phase +-- commit, all case should not raise an error. +SET foreign_twophase_commit TO disabled; +BEGIN; +INSERT INTO ft_1 VALUES (1); +COMMIT; +BEGIN; +INSERT INTO ft_1 VALUES (1); +ROLLBACK; +BEGIN; +INSERT INTO ft_no2pc_1 VALUES (1); +COMMIT; +BEGIN; +INSERT INTO ft_no2pc_1 VALUES (1); +ROLLBACK; +BEGIN; +INSERT INTO ft_2pc_1 VALUES (1); +COMMIT; +BEGIN; +INSERT INTO ft_2pc_1 VALUES (1); +ROLLBACK; +BEGIN; +INSERT INTO ft_2pc_1 VALUES (1); +INSERT INTO ft_no2pc_2 VALUES (1); +COMMIT; +BEGIN; +INSERT INTO ft_2pc_1 VALUES (1); +INSERT INTO ft_2pc_2 VALUES (1); +COMMIT; +-- Test 'required' mode. +-- In this case, when two-phase commit is required, all servers +-- which are involved in the and modified need to support two-phase +-- commit protocol. Otherwise transaction will rollback. +SET foreign_twophase_commit TO 'required'; +-- Ok. Writing only one server doesn't require two-phase commit. +BEGIN; +INSERT INTO ft_1 VALUES (1); +COMMIT; +BEGIN; +INSERT INTO ft_no2pc_1 VALUES (1); +COMMIT; +BEGIN; +INSERT INTO ft_2pc_1 VALUES (1); +COMMIT; +-- Ok. Writing two servers, we require two-phase commit and success. +BEGIN; +INSERT INTO ft_2pc_1 VALUES (1); +INSERT INTO ft_2pc_2 VALUES (1); +COMMIT; +BEGIN; +INSERT INTO t VALUES (1); +INSERT INTO ft_2pc_1 VALUES (1); +COMMIT; +-- Ok. Only reading servers doesn't require two-phase commit. +BEGIN; +SELECT * FROM ft_2pc_1; + i +--- +(0 rows) + +SELECT * FROM ft_2pc_2; + i +--- +(0 rows) + +COMMIT; +BEGIN; +SELECT * FROM ft_1; + i +--- +(0 rows) + +SELECT * FROM ft_no2pc_1; + i +--- +(0 rows) + +COMMIT; +-- Ok. Read one server and write one server. +BEGIN; +SELECT * FROM ft_1; + i +--- +(0 rows) + +INSERT INTO ft_no2pc_1 VALUES (1); +COMMIT; +BEGIN; +SELECT * FROM ft_no2pc_1; + i +--- +(0 rows) + +INSERT INTO ft_2pc_1 VALUES (1); +COMMIT; +-- Error. ft_1 doesn't support two-phase commit. +BEGIN; +INSERT INTO ft_1 VALUES (1); +INSERT INTO ft_2pc_1 VALUES (1); +COMMIT; +ERROR: cannot process a distributed transaction that has operated on a foreign server that does not support two-phase commit protocol +DETAIL: foreign_twophase_commit is 'required' but the transaction has some foreign servers which are not capable of two-phase commit +-- Error. ft_no2pc_1 doesn't support two-phase commit. +BEGIN; +INSERT INTO ft_no2pc_1 VALUES (1); +INSERT INTO ft_2pc_1 VALUES (1); +COMMIT; +ERROR: cannot process a distributed transaction that has operated on a foreign server that does not support two-phase commit protocol +DETAIL: foreign_twophase_commit is 'required' but the transaction has some foreign servers which are not capable of two-phase commit +-- Error. Both ft_1 and ft_2 don't support two-phase commit. +BEGIN; +INSERT INTO ft_1 VALUES (1); +INSERT INTO ft_2 VALUES (1); +COMMIT; +ERROR: cannot process a distributed transaction that has operated on a foreign server that does not support two-phase commit protocol +DETAIL: foreign_twophase_commit is 'required' but the transaction has some foreign servers which are not capable of two-phase commit +-- Error. Both ft_no2pc_1 and ft_no2pc_2 don't support two-phase +-- commit. +BEGIN; +INSERT INTO ft_no2pc_1 VALUES (1); +INSERT INTO ft_no2pc_2 VALUES (1); +COMMIT; +ERROR: cannot process a distributed transaction that has operated on a foreign server that does not support two-phase commit protocol +DETAIL: foreign_twophase_commit is 'required' but the transaction has some foreign servers which are not capable of two-phase commit +-- Error. Two-phase commit is required because of writes on two +-- servers: local node and ft_no2pc_1. But ft_no2pc_1 doesn't support +-- two-phase commit. +BEGIN; +INSERT INTO t VALUES (1); +INSERT INTO ft_no2pc_1 VALUES (1); +COMMIT; +ERROR: cannot process a distributed transaction that has operated on a foreign server that does not support two-phase commit protocol +DETAIL: foreign_twophase_commit is 'required' but the transaction has some foreign servers which are not capable of two-phase commit +-- Tests for PREPARE. +-- Prepare two transactions: local and foreign. +BEGIN; +INSERT INTO ft_2pc_1 VALUES(1); +INSERT INTO t VALUES(3); +PREPARE TRANSACTION 'global_x1'; +SELECT count(*) FROM pg_foreign_xacts(); + count +------- + 1 +(1 row) + +COMMIT PREPARED 'global_x1'; +SELECT count(*) FROM pg_foreign_xacts(); + count +------- + 0 +(1 row) + +-- Even if the transaction modified only one foreign server, +-- we prepare foreign transaction. +BEGIN; +INSERT INTO ft_2pc_1 VALUES(1); +PREPARE TRANSACTION 'global_x1'; +SELECT count(*) FROM pg_foreign_xacts(); + count +------- + 1 +(1 row) + +ROLLBACK PREPARED 'global_x1'; +SELECT count(*) FROM pg_foreign_xacts(); + count +------- + 0 +(1 row) + +-- Error. PREPARE needs all involved foreign servers to +-- support two-phsae commit. +BEGIN; +INSERT INTO ft_no2pc_1 VALUES (1); +PREPARE TRANSACTION 'global_x1'; +ERROR: cannot PREPARE a distributed transaction which has operated on a foreign server not supporting two-phase commit protocol +BEGIN; +INSERT INTO ft_1 VALUES (1); +PREPARE TRANSACTION 'global_x1'; +ERROR: cannot PREPARE a distributed transaction which has operated on a foreign server not supporting two-phase commit protocol +-- Error. We cannot PREPARE a distributed transaction when +-- foreign_twophase_commit is disabled. +SET foreign_twophase_commit TO 'disabled'; +BEGIN; +INSERT INTO ft_2pc_1 VALUES (1); +PREPARE TRANSACTION 'global_x1'; +ERROR: cannot PREPARE a distributed transaction when foreign_twophase_commit is 'disabled' +BEGIN; +INSERT INTO ft_no2pc_1 VALUES (1); +PREPARE TRANSACTION 'global_x1'; +ERROR: cannot PREPARE a distributed transaction when foreign_twophase_commit is 'disabled' +BEGIN; +INSERT INTO ft_1 VALUES (1); +PREPARE TRANSACTION 'global_x1'; +ERROR: cannot PREPARE a distributed transaction when foreign_twophase_commit is 'disabled' diff --git a/src/test/modules/test_fdwxact/fdwxact.conf b/src/test/modules/test_fdwxact/fdwxact.conf new file mode 100644 index 0000000000..20e4a671df --- /dev/null +++ b/src/test/modules/test_fdwxact/fdwxact.conf @@ -0,0 +1,7 @@ +shared_preload_libraries = 'test_fdwxact' +max_prepared_transactions = 10 +max_prepared_foreign_transactions = 10 +max_foreign_transaction_resolvers = 1 +foreign_transaction_resolver_timeout = 0 +foreign_transaction_resolution_retry_interval = 5s +foreign_twophase_commit = disabled diff --git a/src/test/modules/test_fdwxact/sql/test_fdwxact.sql b/src/test/modules/test_fdwxact/sql/test_fdwxact.sql new file mode 100644 index 0000000000..8cf860e295 --- /dev/null +++ b/src/test/modules/test_fdwxact/sql/test_fdwxact.sql @@ -0,0 +1,193 @@ +-- +-- Test for foreign transaction management. +-- + +CREATE EXTENSION test_fdwxact; + +-- setup two servers that don't support transaction management API +CREATE SERVER srv_1 FOREIGN DATA WRAPPER test_fdw; +CREATE SERVER srv_2 FOREIGN DATA WRAPPER test_fdw; + +-- setup two servers that support only commit and rollback API +CREATE SERVER srv_no2pc_1 FOREIGN DATA WRAPPER test_no2pc_fdw; +CREATE SERVER srv_no2pc_2 FOREIGN DATA WRAPPER test_no2pc_fdw; + +-- setup two servers that support commit, rollback and prepare API. +-- That is, those two server support two-phase commit protocol. +CREATE SERVER srv_2pc_1 FOREIGN DATA WRAPPER test_2pc_fdw; +CREATE SERVER srv_2pc_2 FOREIGN DATA WRAPPER test_2pc_fdw; + +CREATE TABLE t (i int); +CREATE FOREIGN TABLE ft_1 (i int) SERVER srv_1; +CREATE FOREIGN TABLE ft_2 (i int) SERVER srv_2; +CREATE FOREIGN TABLE ft_no2pc_1 (i int) SERVER srv_no2pc_1; +CREATE FOREIGN TABLE ft_no2pc_2 (i int) SERVER srv_no2pc_2; +CREATE FOREIGN TABLE ft_2pc_1 (i int) SERVER srv_2pc_1; +CREATE FOREIGN TABLE ft_2pc_2 (i int) SERVER srv_2pc_2; + +CREATE USER MAPPING FOR PUBLIC SERVER srv_1; +CREATE USER MAPPING FOR PUBLIC SERVER srv_2; +CREATE USER MAPPING FOR PUBLIC SERVER srv_no2pc_1; +CREATE USER MAPPING FOR PUBLIC SERVER srv_no2pc_2; +CREATE USER MAPPING FOR PUBLIC SERVER srv_2pc_1; +CREATE USER MAPPING FOR PUBLIC SERVER srv_2pc_2; + + +-- Test 'disabled' mode. +-- Modifies one or two servers but since we don't require two-phase +-- commit, all case should not raise an error. +SET foreign_twophase_commit TO disabled; + +BEGIN; +INSERT INTO ft_1 VALUES (1); +COMMIT; +BEGIN; +INSERT INTO ft_1 VALUES (1); +ROLLBACK; + +BEGIN; +INSERT INTO ft_no2pc_1 VALUES (1); +COMMIT; +BEGIN; +INSERT INTO ft_no2pc_1 VALUES (1); +ROLLBACK; + +BEGIN; +INSERT INTO ft_2pc_1 VALUES (1); +COMMIT; +BEGIN; +INSERT INTO ft_2pc_1 VALUES (1); +ROLLBACK; + +BEGIN; +INSERT INTO ft_2pc_1 VALUES (1); +INSERT INTO ft_no2pc_2 VALUES (1); +COMMIT; + +BEGIN; +INSERT INTO ft_2pc_1 VALUES (1); +INSERT INTO ft_2pc_2 VALUES (1); +COMMIT; + + +-- Test 'required' mode. +-- In this case, when two-phase commit is required, all servers +-- which are involved in the and modified need to support two-phase +-- commit protocol. Otherwise transaction will rollback. +SET foreign_twophase_commit TO 'required'; + +-- Ok. Writing only one server doesn't require two-phase commit. +BEGIN; +INSERT INTO ft_1 VALUES (1); +COMMIT; +BEGIN; +INSERT INTO ft_no2pc_1 VALUES (1); +COMMIT; +BEGIN; +INSERT INTO ft_2pc_1 VALUES (1); +COMMIT; + +-- Ok. Writing two servers, we require two-phase commit and success. +BEGIN; +INSERT INTO ft_2pc_1 VALUES (1); +INSERT INTO ft_2pc_2 VALUES (1); +COMMIT; +BEGIN; +INSERT INTO t VALUES (1); +INSERT INTO ft_2pc_1 VALUES (1); +COMMIT; + +-- Ok. Only reading servers doesn't require two-phase commit. +BEGIN; +SELECT * FROM ft_2pc_1; +SELECT * FROM ft_2pc_2; +COMMIT; +BEGIN; +SELECT * FROM ft_1; +SELECT * FROM ft_no2pc_1; +COMMIT; + +-- Ok. Read one server and write one server. +BEGIN; +SELECT * FROM ft_1; +INSERT INTO ft_no2pc_1 VALUES (1); +COMMIT; +BEGIN; +SELECT * FROM ft_no2pc_1; +INSERT INTO ft_2pc_1 VALUES (1); +COMMIT; + +-- Error. ft_1 doesn't support two-phase commit. +BEGIN; +INSERT INTO ft_1 VALUES (1); +INSERT INTO ft_2pc_1 VALUES (1); +COMMIT; + +-- Error. ft_no2pc_1 doesn't support two-phase commit. +BEGIN; +INSERT INTO ft_no2pc_1 VALUES (1); +INSERT INTO ft_2pc_1 VALUES (1); +COMMIT; + +-- Error. Both ft_1 and ft_2 don't support two-phase commit. +BEGIN; +INSERT INTO ft_1 VALUES (1); +INSERT INTO ft_2 VALUES (1); +COMMIT; + +-- Error. Both ft_no2pc_1 and ft_no2pc_2 don't support two-phase +-- commit. +BEGIN; +INSERT INTO ft_no2pc_1 VALUES (1); +INSERT INTO ft_no2pc_2 VALUES (1); +COMMIT; + +-- Error. Two-phase commit is required because of writes on two +-- servers: local node and ft_no2pc_1. But ft_no2pc_1 doesn't support +-- two-phase commit. +BEGIN; +INSERT INTO t VALUES (1); +INSERT INTO ft_no2pc_1 VALUES (1); +COMMIT; + + +-- Tests for PREPARE. +-- Prepare two transactions: local and foreign. +BEGIN; +INSERT INTO ft_2pc_1 VALUES(1); +INSERT INTO t VALUES(3); +PREPARE TRANSACTION 'global_x1'; +SELECT count(*) FROM pg_foreign_xacts(); +COMMIT PREPARED 'global_x1'; +SELECT count(*) FROM pg_foreign_xacts(); + +-- Even if the transaction modified only one foreign server, +-- we prepare foreign transaction. +BEGIN; +INSERT INTO ft_2pc_1 VALUES(1); +PREPARE TRANSACTION 'global_x1'; +SELECT count(*) FROM pg_foreign_xacts(); +ROLLBACK PREPARED 'global_x1'; +SELECT count(*) FROM pg_foreign_xacts(); + +-- Error. PREPARE needs all involved foreign servers to +-- support two-phsae commit. +BEGIN; +INSERT INTO ft_no2pc_1 VALUES (1); +PREPARE TRANSACTION 'global_x1'; +BEGIN; +INSERT INTO ft_1 VALUES (1); +PREPARE TRANSACTION 'global_x1'; + +-- Error. We cannot PREPARE a distributed transaction when +-- foreign_twophase_commit is disabled. +SET foreign_twophase_commit TO 'disabled'; +BEGIN; +INSERT INTO ft_2pc_1 VALUES (1); +PREPARE TRANSACTION 'global_x1'; +BEGIN; +INSERT INTO ft_no2pc_1 VALUES (1); +PREPARE TRANSACTION 'global_x1'; +BEGIN; +INSERT INTO ft_1 VALUES (1); +PREPARE TRANSACTION 'global_x1'; diff --git a/src/test/modules/test_fdwxact/t/001_basic.pl b/src/test/modules/test_fdwxact/t/001_basic.pl new file mode 100644 index 0000000000..8d48a74e86 --- /dev/null +++ b/src/test/modules/test_fdwxact/t/001_basic.pl @@ -0,0 +1,137 @@ +use strict; +use warnings; +use PostgresNode; +use TestLib; +use Test::More tests => 11; + +my $node = get_new_node('main'); +$node->init; +$node->append_conf('postgresql.conf', qq( +shared_preload_libraries = 'test_fdwxact' +max_prepared_transactions = 10 +max_prepared_foreign_transactions = 10 +max_foreign_transaction_resolvers = 2 +foreign_transaction_resolver_timeout = 0 +foreign_transaction_resolution_retry_interval = 5s +foreign_twophase_commit = required +test_fdwxact.log_api_calls = true + )); +$node->start; + +$node->psql( + 'postgres', " +CREATE EXTENSION test_fdwxact; +CREATE SERVER srv FOREIGN DATA WRAPPER test_fdw; +CREATE SERVER srv_no2pc FOREIGN DATA WRAPPER test_no2pc_fdw; +CREATE SERVER srv_2pc_1 FOREIGN DATA WRAPPER test_2pc_fdw; +CREATE SERVER srv_2pc_2 FOREIGN DATA WRAPPER test_2pc_fdw; + +CREATE TABLE t (i int); +CREATE FOREIGN TABLE ft (i int) SERVER srv; +CREATE FOREIGN TABLE ft_no2pc (i int) SERVER srv_no2pc; +CREATE FOREIGN TABLE ft_2pc_1 (i int) SERVER srv_2pc_1; +CREATE FOREIGN TABLE ft_2pc_2 (i int) SERVER srv_2pc_2; + +CREATE USER MAPPING FOR PUBLIC SERVER srv; +CREATE USER MAPPING FOR PUBLIC SERVER srv_no2pc; +CREATE USER MAPPING FOR PUBLIC SERVER srv_2pc_1; +CREATE USER MAPPING FOR PUBLIC SERVER srv_2pc_2; + "); + +sub run_transaction +{ + my ($node, $prepsql, $sql, $endsql) = @_; + + $endsql = 'COMMIT' unless defined $endsql; + + local $ENV{PGHOST} = $node->host; + local $ENV{PGPORT} = $node->port; + + truncate $node->logfile, 0; + + $node->safe_psql('postgres', $prepsql); + my ($cmdret, $stdout, $stderr) = $node->psql('postgres', + "BEGIN; + SELECT txid_current() as xid; + $sql + $endsql; + "); + my $log = TestLib::slurp_file($node->logfile); + + return $log, $stdout; +} + +my ($log, $xid); + +# The transaction is committed using two-phase commit. +($log, $xid) = run_transaction($node, "", + "INSERT INTO ft_2pc_1 VALUES(1); + INSERT INTO ft_2pc_2 VALUES(1);"); +like($log, qr/commit prepared tx_$xid on srv_2pc_1/, "commit prepared transaction-1"); +like($log, qr/commit prepared tx_$xid on srv_2pc_2/, "commit prepared transaction-2"); + +# Similary, two-phase commit is used. +($log, $xid) = run_transaction($node, "", + "INSERT INTO t VALUES(1); + INSERT INTO ft_2pc_1 VALUES(1);"); +like($log, qr/commit prepared tx_$xid on srv_2pc_1/, "commit prepared transaction-3"); + +# Test the case where transaction attempting prepare the local transaction fails after +# preparing foreign transactions. The first attempt should be succeeded, but the second +# attempt will fail after preparing foreign transaction, and should rollback the prepared +# foreign transaction. +($log, $xid) = run_transaction($node, "", + "INSERT INTO t VALUES(1); + INSERT INTO ft_2pc_1 VALUES(1);", + "PREPARE TRANSACTION 'tx1'"); +($log, $xid) = run_transaction($node, "", + "INSERT INTO t VALUES(1); + INSERT INTO ft_2pc_1 VALUES(1);", + "PREPARE TRANSACTION 'tx1'"); +like($log, qr/rollback prepared tx_$xid on srv_2pc_1/, "failure after prepare transaction"); +$node->safe_psql('postgres', "COMMIT PREPARED 'tx1'"); + +# Inject an error into prepare phase on srv_2pc_1. The transaction fails during +# preparing the foreign transaction on srv_2pc_1. Then, we try to both 'rollback' and +# 'rollback prepared' the foreign transaction, and rollback another foreign +# transaction. +($log, $xid) = run_transaction($node, + "SELECT test_inject_error('error', 'prepare', 'srv_2pc_1');", + "INSERT INTO ft_2pc_1 VALUES(1); + INSERT INTO ft_2pc_2 VALUES(1);"); +like($log, qr/rollback $xid on srv_2pc_1/, "rollback on failed server"); +like($log, qr/rollback prepared tx_$xid on srv_2pc_1/, "rollback prepared on failed server"); +like($log, qr/rollback $xid on srv_2pc_2/, "rollback on another server"); + +# Inject an panic into prepare phase on srv_2pc_2. The server crashes after preparing both +# foreign transaction. After the restart, those transactions are recovered as in-doubt +# transactions. We check if the resolver process rollbacks those transaction after recovery. +($log, $xid) = run_transaction($node, + "SELECT test_inject_error('panic', 'prepare', 'srv_2pc_2');", + "INSERT INTO ft_2pc_1 VALUES(1); + INSERT INTO ft_2pc_2 VALUES(1);"); +$node->restart(); +$node->poll_query_until('postgres', + "SELECT count(*) = 0 FROM pg_foreign_xacts") + or die "Timeout while waiting for resolver process to resolve in-doubt transactions"; +$log = TestLib::slurp_file($node->logfile); +like($log, qr/rollback prepared tx_[0-9]+ on srv_2pc_1/, "resolver rolled back in-doubt transaction"); +like($log, qr/rollback prepared tx_[0-9]+ on srv_2pc_2/, "resolver rolled back in-doubt transaction"); +truncate $node->logfile, 0; + +# Inject an panic into commit phase on srv_2pc_1. The server crashes due to the panic +# error raised by resolver process during commit prepared foreign transaction on srv_2pc_1. +# After the restart, those transactions are recovered as in-doubt transactions. We check if +# the resolver process commits those transaction after recovery. +($log, $xid) = run_transaction($node, + "SELECT test_inject_error('panic', 'commit', 'srv_2pc_1');", + "INSERT INTO ft_2pc_1 VALUES(1); + INSERT INTO ft_2pc_2 VALUES(1);"); +$node->restart(); +$node->poll_query_until('postgres', + "SELECT count(*) = 0 FROM pg_foreign_xacts") + or die "Timeout while waiting for resolver process to resolve in-doubt transactions"; +$log = TestLib::slurp_file($node->logfile); +like($log, qr/commit prepared tx_[0-9]+ on srv_2pc_1/, "resolver rolled back in-doubt transaction"); +like($log, qr/commit prepared tx_[0-9]+ on srv_2pc_2/, "resolver rolled back in-doubt transaction"); +truncate $node->logfile, 0; diff --git a/src/test/modules/test_fdwxact/test_fdwxact--1.0.sql b/src/test/modules/test_fdwxact/test_fdwxact--1.0.sql new file mode 100644 index 0000000000..f676dfe04b --- /dev/null +++ b/src/test/modules/test_fdwxact/test_fdwxact--1.0.sql @@ -0,0 +1,44 @@ +/* src/test/modules/test_atomic_commit/test_atomic_commit--1.0.sql */ + +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "CREATE EXTENSION test_atomic_commit" to load this file. \quit + +-- test_fdw doesn't use transaction API +CREATE FUNCTION test_fdw_handler() +RETURNS fdw_handler +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT; + +CREATE FOREIGN DATA WRAPPER test_fdw + HANDLER test_fdw_handler; + +-- test_no2pc_fdw uses only COMMIT and ROLLBACK API +CREATE FUNCTION test_no2pc_fdw_handler() +RETURNS fdw_handler +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT; + +CREATE FOREIGN DATA WRAPPER test_no2pc_fdw + HANDLER test_no2pc_fdw_handler; + +-- test_2pc uses PREPARE API as well +CREATE FUNCTION test_2pc_fdw_handler() +RETURNS fdw_handler +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT; + +CREATE FOREIGN DATA WRAPPER test_2pc_fdw + HANDLER test_2pc_fdw_handler; + +CREATE FUNCTION test_inject_error( +elevel text, +phase text, +server text) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT; + +CREATE FUNCTION test_reset_error() +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT; diff --git a/src/test/modules/test_fdwxact/test_fdwxact.c b/src/test/modules/test_fdwxact/test_fdwxact.c new file mode 100644 index 0000000000..738690c978 --- /dev/null +++ b/src/test/modules/test_fdwxact/test_fdwxact.c @@ -0,0 +1,471 @@ +/*------------------------------------------------------------------------- + * + * test_fdwxact.c + * Test modules for foreign transaction management + * + * This module implements three types of foreign data wrapper: the first + * doesn't support any transaction FDW APIs, the second supports only + * commit and rollback API and the third supports all transaction API including + * prepare. + * + * Also, this module has an ability to inject an error at prepare callback or + * commit callback using test_inject_error() SQL function. The information of + * injected error is stored in the shared memory so that backend processes and + * resolver processes can see it. + * + * Portions Copyright (c) 2020, PostgreSQL Global Development Group + * + * IDENTIFICATION + * src/test/modules/test_fdwxact/test_fdwxact.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/fdwxact.h" +#include "commands/defrem.h" +#include "access/reloptions.h" +#include "foreign/fdwapi.h" +#include "funcapi.h" +#include "miscadmin.h" +#include "optimizer/clauses.h" +#include "optimizer/cost.h" +#include "optimizer/optimizer.h" +#include "optimizer/pathnode.h" +#include "optimizer/paths.h" +#include "optimizer/planmain.h" +#include "optimizer/restrictinfo.h" +#include "storage/ipc.h" +#include "storage/spin.h" +#include "utils/builtins.h" + +PG_MODULE_MAGIC; + +#define TEST_FDWXCT_MAX_NAME_LEN 32 + +typedef struct testFdwXactSharedState +{ + char elevel[TEST_FDWXCT_MAX_NAME_LEN]; + char phase[TEST_FDWXCT_MAX_NAME_LEN]; + char server[TEST_FDWXCT_MAX_NAME_LEN]; + LWLock *lock; +} testFdwXactSharedState; +testFdwXactSharedState *fxss = NULL; + +static shmem_startup_hook_type prev_shmem_startup_hook = NULL; +static bool log_api_calls = false; + +void _PG_init(void); +void _PG_fini(void); +PG_FUNCTION_INFO_V1(test_fdw_handler); +PG_FUNCTION_INFO_V1(test_no2pc_fdw_handler); +PG_FUNCTION_INFO_V1(test_2pc_fdw_handler); +PG_FUNCTION_INFO_V1(test_inject_error); +PG_FUNCTION_INFO_V1(test_reset_error); + +static void test_fdwxact_shmem_startup(void); +static bool check_event(char *servername, char *phase, int *elevel); +static void testGetForeignRelSize(PlannerInfo *root, + RelOptInfo *baserel, + Oid foreigntableid); +static void testGetForeignPaths(PlannerInfo *root, + RelOptInfo *baserel, + Oid foreigntableid); +static ForeignScan *testGetForeignPlan(PlannerInfo *root, + RelOptInfo *foreignrel, + Oid foreigntableid, + ForeignPath *best_path, + List *tlist, + List *scan_clauses, + Plan *outer_plan); +static void testBeginForeignScan(ForeignScanState *node, int eflags); +static TupleTableSlot *testIterateForeignScan(ForeignScanState *node); +static void testReScanForeignScan(ForeignScanState *node); +static void testEndForeignScan(ForeignScanState *node); +static void testBeginForeignModify(ModifyTableState *mtstate, + ResultRelInfo *resultRelInfo, + List *fdw_private, + int subplan_index, + int eflags); +static TupleTableSlot *testExecForeignInsert(EState *estate, + ResultRelInfo *resultRelInfo, + TupleTableSlot *slot, + TupleTableSlot *planSlot); +static void testEndForeignModify(EState *estate, + ResultRelInfo *resultRelInfo); +static void testBeginForeignInsert(ModifyTableState *mtstate, + ResultRelInfo *resultRelInfo); +static void testEndForeignInsert(EState *estate, + ResultRelInfo *resultRelInfo); +static int testIsForeignRelUpdatable(Relation rel); +static void testPrepareForeignTransaction(FdwXactRslvState *state); +static void testCommitForeignTransaction(FdwXactRslvState *state); +static void testRollbackForeignTransaction(FdwXactRslvState *state); +static char *testGetPrepareId(TransactionId xid, Oid serverid, + Oid userid, int *prep_id_len); + +void +_PG_init(void) +{ + DefineCustomBoolVariable("test_fdwxact.log_api_calls", + "Report transaction API calls to logs.", + NULL, + &log_api_calls, + false, + PGC_USERSET, + 0, + NULL, NULL, NULL); + + RequestAddinShmemSpace(MAXALIGN(sizeof(testFdwXactSharedState))); + RequestNamedLWLockTranche("test_fdwxact", 1); + + prev_shmem_startup_hook = shmem_startup_hook; + shmem_startup_hook = test_fdwxact_shmem_startup; +} + +void +_PG_fini(void) +{ + /* Uninstall hooks. */ + shmem_startup_hook = prev_shmem_startup_hook; +} + +static void +test_fdwxact_shmem_startup(void) +{ + bool found; + + if (prev_shmem_startup_hook) + prev_shmem_startup_hook(); + + fxss = ShmemInitStruct("test_fdwxact", + sizeof(testFdwXactSharedState), + &found); + if (!found) + { + memset(fxss->elevel, 0, TEST_FDWXCT_MAX_NAME_LEN); + memset(fxss->phase, 0, TEST_FDWXCT_MAX_NAME_LEN); + memset(fxss->server, 0, TEST_FDWXCT_MAX_NAME_LEN); + fxss->lock = &(GetNamedLWLockTranche("test_fdwxact"))->lock; + } +} + +Datum +test_fdw_handler(PG_FUNCTION_ARGS) +{ + FdwRoutine *routine = makeNode(FdwRoutine); + + /* Functions for scanning foreign tables */ + routine->GetForeignRelSize = testGetForeignRelSize; + routine->GetForeignPaths = testGetForeignPaths; + routine->GetForeignPlan = testGetForeignPlan; + routine->BeginForeignScan = testBeginForeignScan; + routine->IterateForeignScan = testIterateForeignScan; + routine->ReScanForeignScan = testReScanForeignScan; + routine->EndForeignScan = testEndForeignScan; + + /* Functions for updating foreign tables */ + routine->AddForeignUpdateTargets = NULL; + routine->PlanForeignModify = NULL; + routine->BeginForeignModify = testBeginForeignModify; + routine->ExecForeignInsert = testExecForeignInsert; + routine->EndForeignModify = testEndForeignModify; + routine->BeginForeignInsert = testBeginForeignInsert; + routine->EndForeignInsert = testEndForeignInsert; + routine->IsForeignRelUpdatable = testIsForeignRelUpdatable; + + PG_RETURN_POINTER(routine); +} + +Datum +test_no2pc_fdw_handler(PG_FUNCTION_ARGS) +{ + FdwRoutine *routine = makeNode(FdwRoutine); + + /* Functions for scanning foreign tables */ + routine->GetForeignRelSize = testGetForeignRelSize; + routine->GetForeignPaths = testGetForeignPaths; + routine->GetForeignPlan = testGetForeignPlan; + routine->BeginForeignScan = testBeginForeignScan; + routine->IterateForeignScan = testIterateForeignScan; + routine->ReScanForeignScan = testReScanForeignScan; + routine->EndForeignScan = testEndForeignScan; + + /* Functions for updating foreign tables */ + routine->AddForeignUpdateTargets = NULL; + routine->PlanForeignModify = NULL; + routine->BeginForeignModify = testBeginForeignModify; + routine->ExecForeignInsert = testExecForeignInsert; + routine->EndForeignModify = testEndForeignModify; + routine->BeginForeignInsert = testBeginForeignInsert; + routine->EndForeignInsert = testEndForeignInsert; + routine->IsForeignRelUpdatable = testIsForeignRelUpdatable; + + /* Support only COMMIT and ROLLBACK */ + routine->CommitForeignTransaction = testCommitForeignTransaction; + routine->RollbackForeignTransaction = testRollbackForeignTransaction; + + PG_RETURN_POINTER(routine); +} + +Datum +test_2pc_fdw_handler(PG_FUNCTION_ARGS) +{ + FdwRoutine *routine = makeNode(FdwRoutine); + + /* Functions for scanning foreign tables */ + routine->GetForeignRelSize = testGetForeignRelSize; + routine->GetForeignPaths = testGetForeignPaths; + routine->GetForeignPlan = testGetForeignPlan; + routine->BeginForeignScan = testBeginForeignScan; + routine->IterateForeignScan = testIterateForeignScan; + routine->ReScanForeignScan = testReScanForeignScan; + routine->EndForeignScan = testEndForeignScan; + + /* Functions for updating foreign tables */ + routine->AddForeignUpdateTargets = NULL; + routine->PlanForeignModify = NULL; + routine->BeginForeignModify = testBeginForeignModify; + routine->ExecForeignInsert = testExecForeignInsert; + routine->EndForeignModify = testEndForeignModify; + routine->BeginForeignInsert = testBeginForeignInsert; + routine->EndForeignInsert = testEndForeignInsert; + routine->IsForeignRelUpdatable = testIsForeignRelUpdatable; + + /* Support all functions for foreign transactions */ + routine->GetPrepareId = testGetPrepareId; + routine->PrepareForeignTransaction = testPrepareForeignTransaction; + routine->CommitForeignTransaction = testCommitForeignTransaction; + routine->RollbackForeignTransaction = testRollbackForeignTransaction; + + PG_RETURN_POINTER(routine); +} + +static void +testGetForeignRelSize(PlannerInfo *root, + RelOptInfo *baserel, + Oid foreigntableid) +{ + baserel->pages = 10; + baserel->tuples = 100; +} + +static void +testGetForeignPaths(PlannerInfo *root, + RelOptInfo *baserel, + Oid foreigntableid) +{ + add_path(baserel, (Path *) create_foreignscan_path(root, baserel, + NULL, + 10, 10, 10, + NIL, + baserel->lateral_relids, + NULL, NIL)); +} + +static ForeignScan * +testGetForeignPlan(PlannerInfo *root, + RelOptInfo *foreignrel, + Oid foreigntableid, + ForeignPath *best_path, + List *tlist, + List *scan_clauses, + Plan *outer_plan) +{ + return make_foreignscan(tlist, + NIL, + foreignrel->relid, + NIL, + NULL, + NIL, + NIL, + outer_plan); +} + +static void +testBeginForeignScan(ForeignScanState *node, int eflags) +{ + return; +} + +static TupleTableSlot * +testIterateForeignScan(ForeignScanState *node) +{ + return ExecClearTuple(node->ss.ss_ScanTupleSlot); +} + +static void +testReScanForeignScan(ForeignScanState *node) +{ + return; +} + +static void +testEndForeignScan(ForeignScanState *node) +{ + return; +} + +static void +testBeginForeignModify(ModifyTableState *mtstate, + ResultRelInfo *resultRelInfo, + List *fdw_private, + int subplan_index, + int eflags) +{ + return; +} + +static TupleTableSlot * +testExecForeignInsert(EState *estate, + ResultRelInfo *resultRelInfo, + TupleTableSlot *slot, + TupleTableSlot *planSlot) +{ + return slot; +} + +static void +testEndForeignModify(EState *estate, + ResultRelInfo *resultRelInfo) +{ + return; +} + +static void +testBeginForeignInsert(ModifyTableState *mtstate, + ResultRelInfo *resultRelInfo) +{ + return; +} + +static void +testEndForeignInsert(EState *estate, + ResultRelInfo *resultRelInfo) +{ + return; +} + +static int +testIsForeignRelUpdatable(Relation rel) +{ + /* allow only inserts */ + return (1 << CMD_INSERT); +} + +static char * +testGetPrepareId(TransactionId xid, Oid serverid, + Oid userid, int *prep_id_len) +{ + static char buf[32] = {0}; + + *prep_id_len = snprintf(buf, 32, "tx_%u", xid); + + return buf; +} + +static void +testPrepareForeignTransaction(FdwXactRslvState *state) +{ + int elevel; + + if (check_event(state->server->servername, "prepare", &elevel)) + elog(elevel, "injected error at prepare"); + + if (log_api_calls) + ereport(LOG, (errmsg("prepare %s on %s", + state->fdwxact_id, + state->server->servername))); +} + +static void +testCommitForeignTransaction(FdwXactRslvState *state) +{ + int elevel; + + if (check_event(state->server->servername, "commit", &elevel)) + elog(elevel, "injected error at commit"); + + if (log_api_calls) + { + if (state->flags && FDWXACT_FLAG_ONEPHASE) + ereport(LOG, (errmsg("commit %u on %s", + state->xid, state->server->servername))); + else + ereport(LOG, (errmsg("commit prepared %s on %s", + state->fdwxact_id, + state->server->servername))); + } +} + +static void +testRollbackForeignTransaction(FdwXactRslvState *state) +{ + if (log_api_calls) + { + if (state->flags && FDWXACT_FLAG_ONEPHASE) + ereport(LOG, (errmsg("rollback %u on %s", + state->xid, state->server->servername))); + else + ereport(LOG, (errmsg("rollback prepared %s on %s", + state->fdwxact_id, + state->server->servername))); + } +} + +/* + * Check if an event is set at the phase on the server. If there is, set + * elevel and return true. + */ +static bool +check_event(char *servername, char *phase, int *elevel) +{ + LWLockAcquire(fxss->lock, LW_SHARED); + + if (pg_strcasecmp(fxss->server, servername) != 0 || + pg_strcasecmp(fxss->phase, phase) != 0) + { + LWLockRelease(fxss->lock); + return false; + } + + /* Currently support only error and panic */ + if (pg_strcasecmp(fxss->elevel, "error") == 0) + *elevel = ERROR; + if (pg_strcasecmp(fxss->elevel, "panic") == 0) + *elevel = PANIC; + + LWLockRelease(fxss->lock); + + return true; +} + +/* SQL function to inject an error */ +Datum +test_inject_error(PG_FUNCTION_ARGS) +{ + char *elevel = text_to_cstring(PG_GETARG_TEXT_P(0)); + char *phase = text_to_cstring(PG_GETARG_TEXT_P(1)); + char *server = text_to_cstring(PG_GETARG_TEXT_P(2)); + + LWLockAcquire(fxss->lock, LW_EXCLUSIVE); + strncpy(fxss->elevel, elevel, TEST_FDWXCT_MAX_NAME_LEN); + strncpy(fxss->phase, phase, TEST_FDWXCT_MAX_NAME_LEN); + strncpy(fxss->server, server, TEST_FDWXCT_MAX_NAME_LEN); + LWLockRelease(fxss->lock); + + PG_RETURN_NULL(); +} + +/* SQL function to reset an error */ +Datum +test_reset_error(PG_FUNCTION_ARGS) +{ + LWLockAcquire(fxss->lock, LW_EXCLUSIVE); + memset(fxss->elevel, 0, TEST_FDWXCT_MAX_NAME_LEN); + memset(fxss->phase, 0, TEST_FDWXCT_MAX_NAME_LEN); + memset(fxss->server, 0, TEST_FDWXCT_MAX_NAME_LEN); + LWLockRelease(fxss->lock); + + PG_RETURN_NULL(); +} diff --git a/src/test/modules/test_fdwxact/test_fdwxact.control b/src/test/modules/test_fdwxact/test_fdwxact.control new file mode 100644 index 0000000000..ac9945ba03 --- /dev/null +++ b/src/test/modules/test_fdwxact/test_fdwxact.control @@ -0,0 +1,4 @@ +comment = 'Test code for fdwxact' +default_version = '1.0' +module_pathname = '$libdir/test_fdwxact' +relocatable = true diff --git a/src/test/recovery/Makefile b/src/test/recovery/Makefile index fa8e031526..d47d96975b 100644 --- a/src/test/recovery/Makefile +++ b/src/test/recovery/Makefile @@ -9,7 +9,7 @@ # #------------------------------------------------------------------------- -EXTRA_INSTALL=contrib/test_decoding +EXTRA_INSTALL=contrib/test_decoding contrib/pageinspect contrib/postgres_fdw subdir = src/test/recovery top_builddir = ../../.. diff --git a/src/test/recovery/t/021_fdwxact.pl b/src/test/recovery/t/021_fdwxact.pl new file mode 100644 index 0000000000..9af9bb81dc --- /dev/null +++ b/src/test/recovery/t/021_fdwxact.pl @@ -0,0 +1,175 @@ +# Tests for transaction involving foreign servers +use strict; +use warnings; +use PostgresNode; +use TestLib; +use Test::More tests => 7; + +# Setup master node +my $node_master = get_new_node("master"); +my $node_standby = get_new_node("standby"); + +$node_master->init(allows_streaming => 1); +$node_master->append_conf('postgresql.conf', qq( +max_prepared_transactions = 10 +max_prepared_foreign_transactions = 10 +max_foreign_transaction_resolvers = 2 +foreign_transaction_resolver_timeout = 0 +foreign_transaction_resolution_retry_interval = 5s +foreign_twophase_commit = on +)); +$node_master->start; + +# Take backup from master node +my $backup_name = 'master_backup'; +$node_master->backup($backup_name); + +# Set up standby node +$node_standby->init_from_backup($node_master, $backup_name, + has_streaming => 1); +$node_standby->start; + +# Set up foreign nodes +my $node_fs1 = get_new_node("fs1"); +my $node_fs2 = get_new_node("fs2"); +my $fs1_port = $node_fs1->port; +my $fs2_port = $node_fs2->port; +$node_fs1->init; +$node_fs2->init; +$node_fs1->append_conf('postgresql.conf', qq(max_prepared_transactions = 10)); +$node_fs2->append_conf('postgresql.conf', qq(max_prepared_transactions = 10)); +$node_fs1->start; +$node_fs2->start; + +# Create foreign servers on the master node +$node_master->safe_psql('postgres', qq( +CREATE EXTENSION postgres_fdw +)); +$node_master->safe_psql('postgres', qq( +CREATE SERVER fs1 FOREIGN DATA WRAPPER postgres_fdw +OPTIONS (dbname 'postgres', port '$fs1_port'); +)); +$node_master->safe_psql('postgres', qq( +CREATE SERVER fs2 FOREIGN DATA WRAPPER postgres_fdw +OPTIONS (dbname 'postgres', port '$fs2_port'); +)); + +# Create user mapping on the master node +$node_master->safe_psql('postgres', qq( +CREATE USER MAPPING FOR CURRENT_USER SERVER fs1; +CREATE USER MAPPING FOR CURRENT_USER SERVER fs2; +)); + +# Create tables on foreign nodes and import them to the master node +$node_fs1->safe_psql('postgres', qq( +CREATE SCHEMA fs; +CREATE TABLE fs.t1 (c int); +)); +$node_fs2->safe_psql('postgres', qq( +CREATE SCHEMA fs; +CREATE TABLE fs.t2 (c int); +)); +$node_master->safe_psql('postgres', qq( +IMPORT FOREIGN SCHEMA fs FROM SERVER fs1 INTO public; +IMPORT FOREIGN SCHEMA fs FROM SERVER fs2 INTO public; +CREATE TABLE l_table (c int); +)); + +# Switch to synchronous replication +$node_master->safe_psql('postgres', qq( +ALTER SYSTEM SET synchronous_standby_names ='*'; +)); +$node_master->reload; + +my $result; + +# Prepare two transactions involving multiple foreign servers and shutdown +# the master node. Check if we can commit and rollback the foreign transactions +# after the normal recovery. +$node_master->safe_psql('postgres', qq( +BEGIN; +INSERT INTO t1 VALUES (1); +INSERT INTO t2 VALUES (1); +PREPARE TRANSACTION 'gxid1'; +BEGIN; +INSERT INTO t1 VALUES (2); +INSERT INTO t2 VALUES (2); +PREPARE TRANSACTION 'gxid2'; +)); + +$node_master->stop; +$node_master->start; + +# Commit and rollback foreign transactions after the recovery. +$result = $node_master->psql('postgres', qq(COMMIT PREPARED 'gxid1')); +is($result, 0, 'Commit foreign transactions after recovery'); +$result = $node_master->psql('postgres', qq(ROLLBACK PREPARED 'gxid2')); +is($result, 0, 'Rollback foreign transactions after recovery'); + +# +# Prepare two transactions involving multiple foreign servers and shutdown +# the master node immediately. Check if we can commit and rollback the foreign +# transactions after the crash recovery. +# +$node_master->safe_psql('postgres', qq( +BEGIN; +INSERT INTO t1 VALUES (3); +INSERT INTO t2 VALUES (3); +PREPARE TRANSACTION 'gxid1'; +BEGIN; +INSERT INTO t1 VALUES (4); +INSERT INTO t2 VALUES (4); +PREPARE TRANSACTION 'gxid2'; +)); + +$node_master->teardown_node; +$node_master->start; + +# Commit and rollback foreign transactions after the crash recovery. +$result = $node_master->psql('postgres', qq(COMMIT PREPARED 'gxid1')); +is($result, 0, 'Commit foreign transactions after crash recovery'); +$result = $node_master->psql('postgres', qq(ROLLBACK PREPARED 'gxid2')); +is($result, 0, 'Rollback foreign transactions after crash recovery'); + +# +# Commit transaction involving foreign servers and shutdown the master node +# immediately before checkpoint. Check that WAL replay cleans up +# its shared memory state release locks while replaying transaction commit. +# +$node_master->safe_psql('postgres', qq( +BEGIN; +INSERT INTO t1 VALUES (5); +INSERT INTO t2 VALUES (5); +COMMIT; +)); + +$node_master->teardown_node; +$node_master->start; + +$result = $node_master->safe_psql('postgres', qq( +SELECT count(*) FROM pg_foreign_xacts; +)); +is($result, 0, "Cleanup of shared memory state for foreign transactions"); + +# +# Check if the standby node can process prepared foreign transaction +# after promotion. +# +$node_master->safe_psql('postgres', qq( +BEGIN; +INSERT INTO t1 VALUES (6); +INSERT INTO t2 VALUES (6); +PREPARE TRANSACTION 'gxid1'; +BEGIN; +INSERT INTO t1 VALUES (7); +INSERT INTO t2 VALUES (7); +PREPARE TRANSACTION 'gxid2'; +)); + +$node_master->teardown_node; +$node_standby->promote; + +$result = $node_standby->psql('postgres', qq(COMMIT PREPARED 'gxid1';)); +is($result, 0, 'Commit foreign transaction after promotion'); +$result = $node_standby->psql('postgres', qq(ROLLBACK PREPARED 'gxid2';)); +is($result, 0, 'Rollback foreign transaction after promotion'); diff --git a/src/test/regress/pg_regress.c b/src/test/regress/pg_regress.c index f11a3b9e26..f7d11d9bea 100644 --- a/src/test/regress/pg_regress.c +++ b/src/test/regress/pg_regress.c @@ -2338,9 +2338,12 @@ regression_main(int argc, char *argv[], init_function ifunc, test_function tfunc * Adjust the default postgresql.conf for regression testing. The user * can specify a file to be appended; in any case we expand logging * and set max_prepared_transactions to enable testing of prepared - * xacts. (Note: to reduce the probability of unexpected shmmax - * failures, don't set max_prepared_transactions any higher than - * actually needed by the prepared_xacts regression test.) + * xacts. We also set max_prepared_foreign_transactions and + * max_foreign_transaction_resolvers to enable testing of transaction + * involving multiple foreign servers. (Note: to reduce the probability + * of unexpected shmmax failures, don't set max_prepared_transactions + * any higher than actually needed by the prepared_xacts regression + * test.) */ snprintf(buf, sizeof(buf), "%s/data/postgresql.conf", temp_instance); pg_conf = fopen(buf, "a"); @@ -2355,7 +2358,9 @@ regression_main(int argc, char *argv[], init_function ifunc, test_function tfunc fputs("log_line_prefix = '%m %b[%p] %q%a '\n", pg_conf); fputs("log_lock_waits = on\n", pg_conf); fputs("log_temp_files = 128kB\n", pg_conf); - fputs("max_prepared_transactions = 2\n", pg_conf); + fputs("max_prepared_transactions = 3\n", pg_conf); + fputs("max_prepared_foreign_transactions = 2\n", pg_conf); + fputs("max_foreign_transaction_resolvers = 2\n", pg_conf); for (sl = temp_configs; sl != NULL; sl = sl->next) { -- 2.23.0