diff --git a/src/test/recovery/Makefile b/src/test/recovery/Makefile index 142a1b8..1b28f3c 100644 --- a/src/test/recovery/Makefile +++ b/src/test/recovery/Makefile @@ -21,4 +21,4 @@ check: clean distclean maintainer-clean: rm -rf tmp_check -EXTRA_INSTALL = contrib/test_decoding +EXTRA_INSTALL = contrib/test_decoding contrib/postgres_fdw diff --git a/src/test/recovery/t/010_fdw_xact.pl b/src/test/recovery/t/010_fdw_xact.pl new file mode 100644 index 0000000..58bcefd --- /dev/null +++ b/src/test/recovery/t/010_fdw_xact.pl @@ -0,0 +1,186 @@ +# Tests for transaction involving foreign servers +use strict; +use warnings; +use PostgresNode; +use TestLib; +use Test::More tests => 9; + +# 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_foreign_transactions = 10 +max_prepared_transactions = 10 +)); +$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', "max_prepared_transactions = 10"); +$node_fs2->append_conf('postgresql.conf', "max_prepared_transactions = 10"); +$node_fs1->start; +$node_fs2->start; + +# Create foreign server +$node_master->safe_psql('postgres', "CREATE EXTENSION postgres_fdw"); +$node_master->safe_psql('postgres', " +CREATE SERVER fs1 FOREIGN DATA WRAPPER postgres_fdw +OPTIONS (dbname 'postgres', port '$fs1_port', two_phase_commit 'on'); +"); +$node_master->safe_psql('postgres', " +CREATE SERVER fs2 FOREIGN DATA WRAPPER postgres_fdw +OPTIONS (dbname 'postgres', port '$fs2_port', two_phase_commit 'on'); +"); + +# Create user mapping +$node_master->safe_psql('postgres', " +CREATE USER MAPPING FOR CURRENT_USER SERVER fs1; +CREATE USER MAPPING FOR CURRENT_USER SERVER fs2; +"); + +# Ceate table on foreign server and import them. +$node_fs1->safe_psql('postgres', " +CREATE SCHEMA fs; +CREATE TABLE fs.t1 AS SELECT generate_series(1,10) AS c; +"); +$node_fs2->safe_psql('postgres', " +CREATE SCHEMA fs; +CREATE TABLE fs.t2 AS SELECT generate_series(1,10) AS c; +"); +$node_master->safe_psql('postgres', " +IMPORT FOREIGN SCHEMA fs FROM SERVER fs1 INTO public; +IMPORT FOREIGN SCHEMA fs FROM SERVER fs2 INTO public; +CREATE TABLE local_table (c int); +INSERT INTO local_table SELECT generate_series(1,10); +"); + +# Switch to synchronous replication +$node_master->psql('postgres', "ALTER SYSTEM SET synchronous_standby_names = '*'"); +$node_master->reload; + +my $result; + +# +# Prepare two transaction involving foreign servers. +# Check if we can commit and rollback transaction involving foreign servers after recovery. +# +$node_master->safe_psql('postgres', " +BEGIN; +UPDATE t1 SET c = 1 WHERE c = 1; +UPDATE t2 SET c = 2 WHERE c = 2; +PREPARE TRANSACTION 'gxid1'; +BEGIN; +UPDATE t1 SET c = 3 WHERE c = 3; +UPDATE t2 SET c = 4 WHERE c = 4; +PREPARE TRANSACTION 'gxid2'; +"); + +$node_master->stop; +$node_master->start; + +$result = $node_master->psql('postgres', "COMMIT PREPARED 'gxid1'"); +is($result, 0, 'Commit foreigin transaction after recovery'); +$result = $node_master->psql('postgres', "ROLLBACK PREPARED 'gxid2'"); +is($result, 0, 'Rollback foreigin transaction after recovery'); + +# +# Prepare two transaction involving foreign servers and shutdown master node immediately. +# Check if we can commit and rollback transaction involving foreign servers after crash recovery. +# +$node_master->safe_psql('postgres', " +BEGIN; +UPDATE t1 SET c = 5 WHERE c = 5; +UPDATE t2 SET c = 6 WHERE c = 6; +PREPARE TRANSACTION 'gxid1'; +BEGIN; +UPDATE t1 SET c = 7 WHERE c = 7; +UPDATE t2 SET c = 8 WHERE c = 8; +PREPARE TRANSACTION 'gxid2'; +"); + +$node_master->teardown_node; +$node_master->start; + +$result = $node_master->psql('postgres', "COMMIT PREPARED 'gxid1'"); +is($result, 0, 'Commit foreigin transaction after crash recovery'); +$result = $node_master->psql('postgres', "ROLLBACK PREPARED 'gxid2'"); +is($result, 0, 'Rollback foreigin transaction after crash recovery'); + +# +# Commit transactions involving foreign servers and shutdown master node immediately. +# In this case, information about insertion and deletion of fdw_xact exists at only WAL. +# Check if fdw_xact entry can be processed properly during recovery. +# +$node_master->safe_psql('postgres', " +BEGIN; +UPDATE t1 SET c = 1 WHERE c = 1; +UPDATE t2 SET c = 2 WHERE c = 2; +COMMIT; +"); + +$node_master->teardown_node; +$node_master->start; + +$result = $node_master->safe_psql('postgres', 'SELECT count(*) FROM pg_fdw_xacts'); +is($result, 0, "Remove fdw_xact entry during recovery"); + +# +# A foreign server down after prepared foregin transaction but before commit it. +# Check dangling transaction can be processed propelry by pg_fdw_xact() function. +# +$node_master->safe_psql('postgres', " +BEGIN; +UPDATE t1 SET c = 1 WHERE c = 1; +UPDATE t2 SET c = 2 WHERE c = 2; +PREPARE TRANSACTION 'gxid1'; +"); + +$node_fs1->stop; + +# Since node_fs1 down COMMIT PREPARED will fail on node_fs1. +$node_master->psql('postgres', "COMMIT PREPARED 'gxid1'"); + +$node_fs1->start; +$result = $node_master->safe_psql('postgres', "SELECT count(*) FROM pg_fdw_xact_resolve() WHERE status = 'resolved'"); +is($result, 1, "pg_fdw_xact_resolve function"); + +# +# Check if the standby node can process prepared foreign transaction after +# promotion of the standby server. +# +$node_master->safe_psql('postgres', " +BEGIN; +UPDATE t1 SET c = 5 WHERE c = 5; +UPDATE t2 SET c = 6 WHERE c = 6; +PREPARE TRANSACTION 'gxid1'; +BEGIN; +UPDATE t1 SET c = 7 WHERE c = 7; +UPDATE t2 SET c = 8 WHERE c = 8; +PREPARE TRANSACTION 'gxid2'; +"); + +$node_master->teardown_node; +$node_standby->promote; + +$result = $node_standby->psql('postgres', "COMMIT PREPARED 'gxid1'"); +is($result, 0, 'Commit foreigin transaction after promotion'); +$result = $node_standby->psql('postgres', "ROLLBACK PREPARED 'gxid2'"); +is($result, 0, 'Rollback foreigin transaction after promotion'); +$result = $node_standby->safe_psql('postgres', "SELECT count(*) FROM pg_fdw_xacts"); +is($result, 0, "Check fdw_xact entry on new master node"); diff --git a/src/test/regress/pg_regress.c b/src/test/regress/pg_regress.c index 478260b..3e1ab6a 100644 --- a/src/test/regress/pg_regress.c +++ b/src/test/regress/pg_regress.c @@ -2263,9 +2263,9 @@ 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. We also set max_fdw_transctions to enable testing of atomic - * foreign transactions. (Note: to reduce the probability of unexpected - * shmmax failures, don't set max_prepared_transactions or + * xacts. We also set max_prepared_foreign_transctions to enable testing + * of atomic foreign transactions. (Note: to reduce the probability of + * unexpected shmmax failures, don't set max_prepared_transactions or * max_prepared_foreign_transactions any higher than actually needed by the * corresponding regression tests.). */