summaryrefslogtreecommitdiff
path: root/src/test
diff options
context:
space:
mode:
Diffstat (limited to 'src/test')
-rw-r--r--src/test/regress/expected/memoize.out8
-rw-r--r--src/test/regress/expected/partition_prune.out4
-rw-r--r--src/test/regress/expected/rangefuncs.out8
-rw-r--r--src/test/regress/expected/subselect.out14
-rw-r--r--src/test/regress/expected/union.out14
-rw-r--r--src/test/subscription/Makefile4
-rw-r--r--src/test/subscription/meson.build5
-rw-r--r--src/test/subscription/t/035_conflicts.pl189
8 files changed, 220 insertions, 26 deletions
diff --git a/src/test/regress/expected/memoize.out b/src/test/regress/expected/memoize.out
index 150dc1b44cf..fbcaf113266 100644
--- a/src/test/regress/expected/memoize.out
+++ b/src/test/regress/expected/memoize.out
@@ -545,15 +545,15 @@ EXPLAIN (COSTS OFF)
SELECT * FROM tab_anti t1 WHERE t1.a IN
(SELECT a FROM tab_anti t2 WHERE t2.b IN
(SELECT t1.b FROM tab_anti t3 WHERE t2.a > 1 OFFSET 0));
- QUERY PLAN
--------------------------------------------------
+ QUERY PLAN
+---------------------------------------------------
Nested Loop Semi Join
-> Seq Scan on tab_anti t1
-> Nested Loop Semi Join
Join Filter: (t1.a = t2.a)
-> Seq Scan on tab_anti t2
- -> Subquery Scan on "ANY_subquery"
- Filter: (t2.b = "ANY_subquery".b)
+ -> Subquery Scan on unnamed_subquery
+ Filter: (t2.b = unnamed_subquery.b)
-> Result
One-Time Filter: (t2.a > 1)
-> Seq Scan on tab_anti t3
diff --git a/src/test/regress/expected/partition_prune.out b/src/test/regress/expected/partition_prune.out
index d1966cd7d82..68ecd951809 100644
--- a/src/test/regress/expected/partition_prune.out
+++ b/src/test/regress/expected/partition_prune.out
@@ -4763,7 +4763,7 @@ select min(a) over (partition by a order by a) from part_abc where a >= stable_o
QUERY PLAN
----------------------------------------------------------------------------------------------
Append
- -> Subquery Scan on "*SELECT* 1_1"
+ -> Subquery Scan on unnamed_subquery_2
-> WindowAgg
Window: w1 AS (PARTITION BY part_abc.a ORDER BY part_abc.a)
-> Append
@@ -4780,7 +4780,7 @@ select min(a) over (partition by a order by a) from part_abc where a >= stable_o
-> Index Scan using part_abc_3_2_a_idx on part_abc_3_2 part_abc_4
Index Cond: (a >= (stable_one() + 1))
Filter: (d <= stable_one())
- -> Subquery Scan on "*SELECT* 2"
+ -> Subquery Scan on unnamed_subquery_1
-> WindowAgg
Window: w1 AS (PARTITION BY part_abc_5.a ORDER BY part_abc_5.a)
-> Append
diff --git a/src/test/regress/expected/rangefuncs.out b/src/test/regress/expected/rangefuncs.out
index c21be83aa4a..30241e22da2 100644
--- a/src/test/regress/expected/rangefuncs.out
+++ b/src/test/regress/expected/rangefuncs.out
@@ -2130,10 +2130,10 @@ select testrngfunc();
explain (verbose, costs off)
select * from testrngfunc();
- QUERY PLAN
-----------------------------------------------------------
- Subquery Scan on "*SELECT*"
- Output: "*SELECT*"."?column?", "*SELECT*"."?column?_1"
+ QUERY PLAN
+----------------------------------------------------------------------
+ Subquery Scan on unnamed_subquery
+ Output: unnamed_subquery."?column?", unnamed_subquery."?column?_1"
-> Unique
Output: (1), (2)
-> Sort
diff --git a/src/test/regress/expected/subselect.out b/src/test/regress/expected/subselect.out
index c16dff05bc1..7a1c216a0b1 100644
--- a/src/test/regress/expected/subselect.out
+++ b/src/test/regress/expected/subselect.out
@@ -1692,14 +1692,14 @@ select * from int4_tbl o where (f1, f1) in
-------------------------------------------------------------------
Nested Loop Semi Join
Output: o.f1
- Join Filter: (o.f1 = "ANY_subquery".f1)
+ Join Filter: (o.f1 = unnamed_subquery.f1)
-> Seq Scan on public.int4_tbl o
Output: o.f1
-> Materialize
- Output: "ANY_subquery".f1, "ANY_subquery".g
- -> Subquery Scan on "ANY_subquery"
- Output: "ANY_subquery".f1, "ANY_subquery".g
- Filter: ("ANY_subquery".f1 = "ANY_subquery".g)
+ Output: unnamed_subquery.f1, unnamed_subquery.g
+ -> Subquery Scan on unnamed_subquery
+ Output: unnamed_subquery.f1, unnamed_subquery.g
+ Filter: (unnamed_subquery.f1 = unnamed_subquery.g)
-> Result
Output: i.f1, ((generate_series(1, 50)) / 10)
-> ProjectSet
@@ -2867,8 +2867,8 @@ ON B.hundred in (SELECT min(c.hundred) FROM tenk2 C WHERE c.odd = b.odd);
-> Memoize
Cache Key: b.hundred, b.odd
Cache Mode: binary
- -> Subquery Scan on "ANY_subquery"
- Filter: (b.hundred = "ANY_subquery".min)
+ -> Subquery Scan on unnamed_subquery
+ Filter: (b.hundred = unnamed_subquery.min)
-> Result
InitPlan 1
-> Limit
diff --git a/src/test/regress/expected/union.out b/src/test/regress/expected/union.out
index 96962817ed4..d3ea433db15 100644
--- a/src/test/regress/expected/union.out
+++ b/src/test/regress/expected/union.out
@@ -942,7 +942,7 @@ SELECT q1 FROM int8_tbl EXCEPT SELECT q2 FROM int8_tbl ORDER BY q2 LIMIT 1;
ERROR: column "q2" does not exist
LINE 1: ... int8_tbl EXCEPT SELECT q2 FROM int8_tbl ORDER BY q2 LIMIT 1...
^
-DETAIL: There is a column named "q2" in table "*SELECT* 2", but it cannot be referenced from this part of the query.
+DETAIL: There is a column named "q2" in table "unnamed_subquery", but it cannot be referenced from this part of the query.
-- But this should work:
SELECT q1 FROM int8_tbl EXCEPT (((SELECT q2 FROM int8_tbl ORDER BY q2 LIMIT 1))) ORDER BY 1;
q1
@@ -1338,14 +1338,14 @@ where q2 = q2;
----------------------------------------------------------
Unique
-> Merge Append
- Sort Key: "*SELECT* 1".q1
- -> Subquery Scan on "*SELECT* 1"
+ Sort Key: unnamed_subquery.q1
+ -> Subquery Scan on unnamed_subquery
-> Unique
-> Sort
Sort Key: i81.q1, i81.q2
-> Seq Scan on int8_tbl i81
Filter: (q2 IS NOT NULL)
- -> Subquery Scan on "*SELECT* 2"
+ -> Subquery Scan on unnamed_subquery_1
-> Unique
-> Sort
Sort Key: i82.q1, i82.q2
@@ -1374,14 +1374,14 @@ where -q1 = q2;
--------------------------------------------------------
Unique
-> Merge Append
- Sort Key: "*SELECT* 1".q1
- -> Subquery Scan on "*SELECT* 1"
+ Sort Key: unnamed_subquery.q1
+ -> Subquery Scan on unnamed_subquery
-> Unique
-> Sort
Sort Key: i81.q1, i81.q2
-> Seq Scan on int8_tbl i81
Filter: ((- q1) = q2)
- -> Subquery Scan on "*SELECT* 2"
+ -> Subquery Scan on unnamed_subquery_1
-> Unique
-> Sort
Sort Key: i82.q1, i82.q2
diff --git a/src/test/subscription/Makefile b/src/test/subscription/Makefile
index 50b65d8f6ea..9d97e7d5c0d 100644
--- a/src/test/subscription/Makefile
+++ b/src/test/subscription/Makefile
@@ -13,9 +13,11 @@ subdir = src/test/subscription
top_builddir = ../../..
include $(top_builddir)/src/Makefile.global
-EXTRA_INSTALL = contrib/hstore
+EXTRA_INSTALL = contrib/hstore \
+ src/test/modules/injection_points
export with_icu
+export enable_injection_points
check:
$(prove_check)
diff --git a/src/test/subscription/meson.build b/src/test/subscription/meson.build
index 586ffba434e..20b4e523d93 100644
--- a/src/test/subscription/meson.build
+++ b/src/test/subscription/meson.build
@@ -5,7 +5,10 @@ tests += {
'sd': meson.current_source_dir(),
'bd': meson.current_build_dir(),
'tap': {
- 'env': {'with_icu': icu.found() ? 'yes' : 'no'},
+ 'env': {
+ 'with_icu': icu.found() ? 'yes' : 'no',
+ 'enable_injection_points': get_option('injection_points') ? 'yes' : 'no',
+ },
'tests': [
't/001_rep_changes.pl',
't/002_types.pl',
diff --git a/src/test/subscription/t/035_conflicts.pl b/src/test/subscription/t/035_conflicts.pl
index 51b23a39fa9..db0d5b464e8 100644
--- a/src/test/subscription/t/035_conflicts.pl
+++ b/src/test/subscription/t/035_conflicts.pl
@@ -387,6 +387,195 @@ ok( $logfile =~
'update target row was deleted in tab');
###############################################################################
+# Check that the xmin value of the conflict detection slot can be advanced when
+# the subscription has no tables.
+###############################################################################
+
+# Remove the table from the publication
+$node_B->safe_psql('postgres', "ALTER PUBLICATION tap_pub_B DROP TABLE tab");
+
+$node_A->safe_psql('postgres',
+ "ALTER SUBSCRIPTION $subname_AB REFRESH PUBLICATION");
+
+# Remember the next transaction ID to be assigned
+$next_xid = $node_A->safe_psql('postgres', "SELECT txid_current() + 1;");
+
+# Confirm that the xmin value is advanced to the latest nextXid. If no
+# transactions are running, the apply worker selects nextXid as the candidate
+# for the non-removable xid. See GetOldestActiveTransactionId().
+ok( $node_A->poll_query_until(
+ 'postgres',
+ "SELECT xmin = $next_xid from pg_replication_slots WHERE slot_name = 'pg_conflict_detection'"
+ ),
+ "the xmin value of slot 'pg_conflict_detection' is updated on Node A");
+
+# Re-add the table to the publication for further tests
+$node_B->safe_psql('postgres', "ALTER PUBLICATION tap_pub_B ADD TABLE tab");
+
+$node_A->safe_psql('postgres',
+ "ALTER SUBSCRIPTION $subname_AB REFRESH PUBLICATION WITH (copy_data = false)");
+
+###############################################################################
+# Test that publisher's transactions marked with DELAY_CHKPT_IN_COMMIT prevent
+# concurrently deleted tuples on the subscriber from being removed. This test
+# also acts as a safeguard to prevent developers from moving the commit
+# timestamp acquisition before marking DELAY_CHKPT_IN_COMMIT in
+# RecordTransactionCommitPrepared.
+###############################################################################
+
+my $injection_points_supported = $node_B->check_extension('injection_points');
+
+# This test depends on an injection point to block the prepared transaction
+# commit after marking DELAY_CHKPT_IN_COMMIT flag.
+if ($injection_points_supported != 0)
+{
+ $node_B->append_conf('postgresql.conf',
+ "shared_preload_libraries = 'injection_points'
+ max_prepared_transactions = 1");
+ $node_B->restart;
+
+ # Disable the subscription on Node B for testing only one-way
+ # replication.
+ $node_B->psql('postgres', "ALTER SUBSCRIPTION $subname_BA DISABLE;");
+
+ # Wait for the apply worker to stop
+ $node_B->poll_query_until('postgres',
+ "SELECT count(*) = 0 FROM pg_stat_activity WHERE backend_type = 'logical replication apply worker'"
+ );
+
+ # Truncate the table to cleanup existing dead rows in the table. Then insert
+ # a new row.
+ $node_B->safe_psql(
+ 'postgres', qq(
+ TRUNCATE tab;
+ INSERT INTO tab VALUES(1, 1);
+ ));
+
+ $node_B->wait_for_catchup($subname_AB);
+
+ # Create the injection_points extension on the publisher node and attach to the
+ # commit-after-delay-checkpoint injection point.
+ $node_B->safe_psql(
+ 'postgres',
+ "CREATE EXTENSION injection_points;
+ SELECT injection_points_attach('commit-after-delay-checkpoint', 'wait');"
+ );
+
+ # Start a background session on the publisher node to perform an update and
+ # pause at the injection point.
+ my $pub_session = $node_B->background_psql('postgres');
+ $pub_session->query_until(
+ qr/starting_bg_psql/,
+ q{
+ \echo starting_bg_psql
+ BEGIN;
+ UPDATE tab SET b = 2 WHERE a = 1;
+ PREPARE TRANSACTION 'txn_with_later_commit_ts';
+ COMMIT PREPARED 'txn_with_later_commit_ts';
+ }
+ );
+
+ # Confirm the update is suspended
+ $result =
+ $node_B->safe_psql('postgres', 'SELECT * FROM tab WHERE a = 1');
+ is($result, qq(1|1), 'publisher sees the old row');
+
+ # Delete the row on the subscriber. The deleted row should be retained due to a
+ # transaction on the publisher, which is currently marked with the
+ # DELAY_CHKPT_IN_COMMIT flag.
+ $node_A->safe_psql('postgres', "DELETE FROM tab WHERE a = 1;");
+
+ # Get the commit timestamp for the delete
+ my $sub_ts = $node_A->safe_psql('postgres',
+ "SELECT timestamp FROM pg_last_committed_xact();");
+
+ $log_location = -s $node_A->logfile;
+
+ # Confirm that the apply worker keeps requesting publisher status, while
+ # awaiting the prepared transaction to commit. Thus, the request log should
+ # appear more than once.
+ $node_A->wait_for_log(
+ qr/sending publisher status request message/,
+ $log_location);
+
+ $log_location = -s $node_A->logfile;
+
+ $node_A->wait_for_log(
+ qr/sending publisher status request message/,
+ $log_location);
+
+ # Confirm that the dead tuple cannot be removed
+ ($cmdret, $stdout, $stderr) =
+ $node_A->psql('postgres', qq(VACUUM (verbose) public.tab;));
+
+ ok($stderr =~ qr/1 are dead but not yet removable/,
+ 'the deleted column is non-removable');
+
+ $log_location = -s $node_A->logfile;
+
+ # Wakeup and detach the injection point on the publisher node. The prepared
+ # transaction should now commit.
+ $node_B->safe_psql(
+ 'postgres',
+ "SELECT injection_points_wakeup('commit-after-delay-checkpoint');
+ SELECT injection_points_detach('commit-after-delay-checkpoint');"
+ );
+
+ # Close the background session on the publisher node
+ ok($pub_session->quit, "close publisher session");
+
+ # Confirm that the transaction committed
+ $result =
+ $node_B->safe_psql('postgres', 'SELECT * FROM tab WHERE a = 1');
+ is($result, qq(1|2), 'publisher sees the new row');
+
+ # Ensure the UPDATE is replayed on subscriber
+ $node_B->wait_for_catchup($subname_AB);
+
+ $logfile = slurp_file($node_A->logfile(), $log_location);
+ ok( $logfile =~
+ qr/conflict detected on relation "public.tab": conflict=update_deleted.*
+.*DETAIL:.* The row to be updated was deleted locally in transaction [0-9]+ at .*
+.*Remote row \(1, 2\); replica identity full \(1, 1\)/,
+ 'update target row was deleted in tab');
+
+ # Remember the next transaction ID to be assigned
+ $next_xid =
+ $node_A->safe_psql('postgres', "SELECT txid_current() + 1;");
+
+ # Confirm that the xmin value is advanced to the latest nextXid after the
+ # prepared transaction on the publisher has been committed.
+ ok( $node_A->poll_query_until(
+ 'postgres',
+ "SELECT xmin = $next_xid from pg_replication_slots WHERE slot_name = 'pg_conflict_detection'"
+ ),
+ "the xmin value of slot 'pg_conflict_detection' is updated on subscriber"
+ );
+
+ # Confirm that the dead tuple can be removed now
+ ($cmdret, $stdout, $stderr) =
+ $node_A->psql('postgres', qq(VACUUM (verbose) public.tab;));
+
+ ok($stderr =~ qr/1 removed, 0 remain, 0 are dead but not yet removable/,
+ 'the deleted column is removed');
+
+ # Get the commit timestamp for the publisher's update
+ my $pub_ts = $node_B->safe_psql('postgres',
+ "SELECT pg_xact_commit_timestamp(xmin) from tab where a=1;");
+
+ # Check that the commit timestamp for the update on the publisher is later than
+ # or equal to the timestamp of the local deletion, as the commit timestamp
+ # should be assigned after marking the DELAY_CHKPT_IN_COMMIT flag.
+ $result = $node_B->safe_psql('postgres',
+ "SELECT '$pub_ts'::timestamp >= '$sub_ts'::timestamp");
+ is($result, qq(t),
+ "pub UPDATE's timestamp is later than that of sub's DELETE");
+
+ # Re-enable the subscription for further tests
+ $node_B->psql('postgres', "ALTER SUBSCRIPTION $subname_BA ENABLE;");
+}
+
+###############################################################################
# Check that dead tuple retention stops due to the wait time surpassing
# max_retention_duration.
###############################################################################