From c51e0cc7fe46934e681018b2c19bcb96708883cc Mon Sep 17 00:00:00 2001 From: Vignesh C Date: Wed, 22 Apr 2026 21:24:56 +0530 Subject: [PATCH] replication: fix translation issues in tuple value detail messages append_tuple_value_detail() constructed user-visible messages using separately translated fragments such as ": ", ", ", and ".",. This makes correct translation difficult or impossible in some languages. Refactor append_tuple_value_detail() to build only a SQL-style tuple representation like "(v1, v2, v3)" without any translated text. Move all punctuation and sentence construction to the callers, which now use a single translatable string with a %s placeholder for the tuple data. --- src/backend/replication/logical/conflict.c | 143 ++++++++++----------- src/test/subscription/t/001_rep_changes.pl | 6 +- src/test/subscription/t/013_partition.pl | 14 +- src/test/subscription/t/029_on_error.pl | 2 +- src/test/subscription/t/030_origin.pl | 4 +- src/test/subscription/t/035_conflicts.pl | 30 ++--- 6 files changed, 98 insertions(+), 101 deletions(-) diff --git a/src/backend/replication/logical/conflict.c b/src/backend/replication/logical/conflict.c index 2887dfb7150..33da7a7755b 100644 --- a/src/backend/replication/logical/conflict.c +++ b/src/backend/replication/logical/conflict.c @@ -192,13 +192,14 @@ errcode_apply_conflict(ConflictType type) * local row, remote row, and replica identity columns. */ static void -append_tuple_value_detail(StringInfo buf, List *tuple_values, - bool need_newline) +append_tuple_value_detail(StringInfo buf, List *tuple_values) { bool first = true; Assert(buf != NULL && tuple_values != NIL); + appendStringInfoChar(buf, '('); + foreach_ptr(char, tuple_value, tuple_values) { /* @@ -209,34 +210,14 @@ append_tuple_value_detail(StringInfo buf, List *tuple_values, if (!tuple_value) continue; - if (first) - { - /* - * translator: The colon is used as a separator in conflict - * messages. The first part, built in the caller, describes what - * happened locally; the second part lists the conflicting keys - * and tuple data. - */ - appendStringInfoString(buf, _(": ")); - } - else - { - /* - * translator: This is a separator in a list of conflicting keys - * and tuple data. - */ - appendStringInfoString(buf, _(", ")); - } + if (!first) + appendStringInfoString(buf, ", "); appendStringInfoString(buf, tuple_value); first = false; } - /* translator: This is the terminator of a conflict message */ - appendStringInfoString(buf, _(".")); - - if (need_newline) - appendStringInfoChar(buf, '\n'); + appendStringInfoChar(buf, ')'); } /* @@ -258,6 +239,7 @@ errdetail_apply_conflict(EState *estate, ResultRelInfo *relinfo, StringInfo err_msg) { StringInfoData err_detail; + StringInfoData tuple_buf; char *origin_name; char *key_desc = NULL; char *local_desc = NULL; @@ -272,6 +254,7 @@ errdetail_apply_conflict(EState *estate, ResultRelInfo *relinfo, indexoid); initStringInfo(&err_detail); + initStringInfo(&tuple_buf); /* Construct a detailed message describing the type of conflict */ switch (type) @@ -284,23 +267,28 @@ errdetail_apply_conflict(EState *estate, ResultRelInfo *relinfo, if (err_msg->len == 0) { - appendStringInfoString(&err_detail, _("Could not apply remote change")); - - append_tuple_value_detail(&err_detail, - list_make2(remote_desc, search_desc), - true); + append_tuple_value_detail(&tuple_buf, + list_make2(remote_desc, search_desc)); + appendStringInfo(&err_detail, _("Could not apply remote change: %s.\n"), + tuple_buf.data); + resetStringInfo(&tuple_buf); } + append_tuple_value_detail(&tuple_buf, + list_make2(key_desc, local_desc)); + if (localts) { if (localorigin == InvalidReplOriginId) - appendStringInfo(&err_detail, _("Key already exists in unique index \"%s\", modified locally in transaction %u at %s"), + appendStringInfo(&err_detail, _("Key already exists in unique index \"%s\", modified locally in transaction %u at %s: %s."), get_rel_name(indexoid), - localxmin, timestamptz_to_str(localts)); + localxmin, timestamptz_to_str(localts), + tuple_buf.data); else if (replorigin_by_oid(localorigin, true, &origin_name)) - appendStringInfo(&err_detail, _("Key already exists in unique index \"%s\", modified by origin \"%s\" in transaction %u at %s"), + appendStringInfo(&err_detail, _("Key already exists in unique index \"%s\", modified by origin \"%s\" in transaction %u at %s: %s."), get_rel_name(indexoid), origin_name, - localxmin, timestamptz_to_str(localts)); + localxmin, timestamptz_to_str(localts), + tuple_buf.data); /* * The origin that modified this row has been removed. This @@ -310,44 +298,47 @@ errdetail_apply_conflict(EState *estate, ResultRelInfo *relinfo, * manually dropped by the user. */ else - appendStringInfo(&err_detail, _("Key already exists in unique index \"%s\", modified by a non-existent origin in transaction %u at %s"), + appendStringInfo(&err_detail, _("Key already exists in unique index \"%s\", modified by a non-existent origin in transaction %u at %s: %s."), get_rel_name(indexoid), - localxmin, timestamptz_to_str(localts)); + localxmin, timestamptz_to_str(localts), + tuple_buf.data); } else - appendStringInfo(&err_detail, _("Key already exists in unique index \"%s\", modified in transaction %u"), - get_rel_name(indexoid), localxmin); - - append_tuple_value_detail(&err_detail, - list_make2(key_desc, local_desc), false); + appendStringInfo(&err_detail, _("Key already exists in unique index \"%s\", modified in transaction %u: %s."), + get_rel_name(indexoid), localxmin, + tuple_buf.data); break; case CT_UPDATE_ORIGIN_DIFFERS: + append_tuple_value_detail(&tuple_buf, + list_make3(local_desc, remote_desc, + search_desc)); + if (localorigin == InvalidReplOriginId) - appendStringInfo(&err_detail, _("Updating the row that was modified locally in transaction %u at %s"), - localxmin, timestamptz_to_str(localts)); + appendStringInfo(&err_detail, _("Updating the row that was modified locally in transaction %u at %s: %s."), + localxmin, timestamptz_to_str(localts), + tuple_buf.data); else if (replorigin_by_oid(localorigin, true, &origin_name)) - appendStringInfo(&err_detail, _("Updating the row that was modified by a different origin \"%s\" in transaction %u at %s"), - origin_name, localxmin, timestamptz_to_str(localts)); + appendStringInfo(&err_detail, _("Updating the row that was modified by a different origin \"%s\" in transaction %u at %s: %s."), + origin_name, localxmin, + timestamptz_to_str(localts), + tuple_buf.data); /* The origin that modified this row has been removed. */ else - appendStringInfo(&err_detail, _("Updating the row that was modified by a non-existent origin in transaction %u at %s"), - localxmin, timestamptz_to_str(localts)); - - append_tuple_value_detail(&err_detail, - list_make3(local_desc, remote_desc, - search_desc), false); + appendStringInfo(&err_detail, _("Updating the row that was modified by a non-existent origin in transaction %u at %s: %s."), + localxmin, timestamptz_to_str(localts), + tuple_buf.data); break; case CT_UPDATE_DELETED: - appendStringInfoString(&err_detail, _("Could not find the row to be updated")); - append_tuple_value_detail(&err_detail, - list_make2(remote_desc, search_desc), - true); + append_tuple_value_detail(&tuple_buf, + list_make2(remote_desc, search_desc)); + appendStringInfo(&err_detail, _("Could not find the row to be updated: %s.\n"), + tuple_buf.data); if (localts) { @@ -369,44 +360,50 @@ errdetail_apply_conflict(EState *estate, ResultRelInfo *relinfo, break; case CT_UPDATE_MISSING: - appendStringInfoString(&err_detail, _("Could not find the row to be updated")); - - append_tuple_value_detail(&err_detail, - list_make2(remote_desc, search_desc), - false); + append_tuple_value_detail(&tuple_buf, + list_make2(remote_desc, search_desc)); + appendStringInfo(&err_detail, _("Could not find the row to be updated: %s."), + tuple_buf.data); break; case CT_DELETE_ORIGIN_DIFFERS: + append_tuple_value_detail(&tuple_buf, + list_make3(local_desc, remote_desc, + search_desc)); + if (localorigin == InvalidReplOriginId) - appendStringInfo(&err_detail, _("Deleting the row that was modified locally in transaction %u at %s"), - localxmin, timestamptz_to_str(localts)); + appendStringInfo(&err_detail, _("Deleting the row that was modified locally in transaction %u at %s: %s."), + localxmin, timestamptz_to_str(localts), + tuple_buf.data); else if (replorigin_by_oid(localorigin, true, &origin_name)) - appendStringInfo(&err_detail, _("Deleting the row that was modified by a different origin \"%s\" in transaction %u at %s"), - origin_name, localxmin, timestamptz_to_str(localts)); + appendStringInfo(&err_detail, _("Deleting the row that was modified by a different origin \"%s\" in transaction %u at %s: %s."), + origin_name, localxmin, + timestamptz_to_str(localts), + tuple_buf.data); /* The origin that modified this row has been removed. */ else - appendStringInfo(&err_detail, _("Deleting the row that was modified by a non-existent origin in transaction %u at %s"), - localxmin, timestamptz_to_str(localts)); - - append_tuple_value_detail(&err_detail, - list_make3(local_desc, remote_desc, - search_desc), false); + appendStringInfo(&err_detail, _("Deleting the row that was modified by a non-existent origin in transaction %u at %s: %s."), + localxmin, timestamptz_to_str(localts), + tuple_buf.data); break; case CT_DELETE_MISSING: - appendStringInfoString(&err_detail, _("Could not find the row to be deleted")); + append_tuple_value_detail(&tuple_buf, + list_make1(search_desc)); - append_tuple_value_detail(&err_detail, - list_make1(search_desc), false); + appendStringInfo(&err_detail, _("Could not find the row to be deleted: %s."), + tuple_buf.data); break; } Assert(err_detail.len > 0); + pfree(tuple_buf.data); + /* * Insert a blank line to visually separate the new detail line from the * existing ones. diff --git a/src/test/subscription/t/001_rep_changes.pl b/src/test/subscription/t/001_rep_changes.pl index 7d41715ed81..50cfb434e0b 100644 --- a/src/test/subscription/t/001_rep_changes.pl +++ b/src/test/subscription/t/001_rep_changes.pl @@ -367,15 +367,15 @@ $node_publisher->wait_for_catchup('tap_sub'); my $logfile = slurp_file($node_subscriber->logfile, $log_location_sub); like( $logfile, - qr/conflict detected on relation "public.tab_full_pk": conflict=update_missing.*\n.*DETAIL:.* Could not find the row to be updated: remote row \(1, quux\), replica identity \(a\)=\(1\)/m, + qr/conflict detected on relation "public.tab_full_pk": conflict=update_missing.*\n.*DETAIL:.* Could not find the row to be updated: \(remote row \(1, quux\), replica identity \(a\)=\(1\)\)/m, 'update target row is missing'); like( $logfile, - qr/conflict detected on relation "public.tab_full": conflict=update_missing.*\n.*DETAIL:.* Could not find the row to be updated: remote row \(26\), replica identity full \(25\)/m, + qr/conflict detected on relation "public.tab_full": conflict=update_missing.*\n.*DETAIL:.* Could not find the row to be updated: \(remote row \(26\), replica identity full \(25\)\)/m, 'update target row is missing'); like( $logfile, - qr/conflict detected on relation "public.tab_full_pk": conflict=delete_missing.*\n.*DETAIL:.* Could not find the row to be deleted: replica identity \(a\)=\(2\)/m, + qr/conflict detected on relation "public.tab_full_pk": conflict=delete_missing.*\n.*DETAIL:.* Could not find the row to be deleted: \(replica identity \(a\)=\(2\)\)/m, 'delete target row is missing'); $node_subscriber->append_conf('postgresql.conf', diff --git a/src/test/subscription/t/013_partition.pl b/src/test/subscription/t/013_partition.pl index 234d4f003b7..c904e5c50a4 100644 --- a/src/test/subscription/t/013_partition.pl +++ b/src/test/subscription/t/013_partition.pl @@ -369,19 +369,19 @@ $node_publisher->wait_for_catchup('sub2'); my $logfile = slurp_file($node_subscriber1->logfile(), $log_location); like( $logfile, - qr/conflict detected on relation "public.tab1_2_2": conflict=update_missing.*\n.*DETAIL:.* Could not find the row to be updated: remote row \(null, 4, quux\), replica identity \(a\)=\(4\)/, + qr/conflict detected on relation "public.tab1_2_2": conflict=update_missing.*\n.*DETAIL:.* Could not find the row to be updated: \(remote row \(null, 4, quux\), replica identity \(a\)=\(4\)\)/, 'update target row is missing in tab1_2_2'); like( $logfile, - qr/conflict detected on relation "public.tab1_1": conflict=delete_missing.*\n.*DETAIL:.* Could not find the row to be deleted: replica identity \(a\)=\(1\)/, + qr/conflict detected on relation "public.tab1_1": conflict=delete_missing.*\n.*DETAIL:.* Could not find the row to be deleted: \(replica identity \(a\)=\(1\)\)/, 'delete target row is missing in tab1_1'); like( $logfile, - qr/conflict detected on relation "public.tab1_2_2": conflict=delete_missing.*\n.*DETAIL:.* Could not find the row to be deleted: replica identity \(a\)=\(4\)/, + qr/conflict detected on relation "public.tab1_2_2": conflict=delete_missing.*\n.*DETAIL:.* Could not find the row to be deleted: \(replica identity \(a\)=\(4\)\)/, 'delete target row is missing in tab1_2_2'); like( $logfile, - qr/conflict detected on relation "public.tab1_def": conflict=delete_missing.*\n.*DETAIL:.* Could not find the row to be deleted: replica identity \(a\)=\(10\)/, + qr/conflict detected on relation "public.tab1_def": conflict=delete_missing.*\n.*DETAIL:.* Could not find the row to be deleted: \(replica identity \(a\)=\(10\)\)/, 'delete target row is missing in tab1_def'); # Tests for replication using root table identity and schema @@ -786,11 +786,11 @@ $node_publisher->wait_for_catchup('sub2'); $logfile = slurp_file($node_subscriber1->logfile(), $log_location); like( $logfile, - qr/conflict detected on relation "public.tab2_1": conflict=update_missing.*\n.*DETAIL:.* Could not find the row to be updated: remote row \(pub_tab2, quux, 5\), replica identity \(a\)=\(5\)/, + qr/conflict detected on relation "public.tab2_1": conflict=update_missing.*\n.*DETAIL:.* Could not find the row to be updated: \(remote row \(pub_tab2, quux, 5\), replica identity \(a\)=\(5\)\)/, 'update target row is missing in tab2_1'); like( $logfile, - qr/conflict detected on relation "public.tab2_1": conflict=delete_missing.*\n.*DETAIL:.* Could not find the row to be deleted: replica identity \(a\)=\(1\)/, + qr/conflict detected on relation "public.tab2_1": conflict=delete_missing.*\n.*DETAIL:.* Could not find the row to be deleted: \(replica identity \(a\)=\(1\)\)/, 'delete target row is missing in tab2_1'); # Enable the track_commit_timestamp to detect the conflict when attempting @@ -809,7 +809,7 @@ $node_publisher->wait_for_catchup('sub_viaroot'); $logfile = slurp_file($node_subscriber1->logfile(), $log_location); like( $logfile, - qr/conflict detected on relation "public.tab2_1": conflict=update_origin_differs.*\n.*DETAIL:.* Updating the row that was modified locally in transaction [0-9]+ at .*: local row \(yyy, null, 3\), remote row \(pub_tab2, quux, 3\), replica identity \(a\)=\(3\)./, + qr/conflict detected on relation "public.tab2_1": conflict=update_origin_differs.*\n.*DETAIL:.* Updating the row that was modified locally in transaction [0-9]+ at .*: \(local row \(yyy, null, 3\), remote row \(pub_tab2, quux, 3\), replica identity \(a\)=\(3\)\)./, 'updating a row that was modified by a different origin'); # The remaining tests no longer test conflict detection. diff --git a/src/test/subscription/t/029_on_error.pl b/src/test/subscription/t/029_on_error.pl index 7d68759b6cd..e14081add40 100644 --- a/src/test/subscription/t/029_on_error.pl +++ b/src/test/subscription/t/029_on_error.pl @@ -30,7 +30,7 @@ sub test_skip_lsn # ERROR with its CONTEXT when retrieving this information. my $contents = slurp_file($node_subscriber->logfile, $offset); $contents =~ - qr/conflict detected on relation "public.tbl".*\n.*DETAIL:.* Could not apply remote change.*\n.*Key already exists in unique index "tbl_pkey", modified by .*origin.* in transaction \d+ at .*: key .*, local row .*\n.*CONTEXT:.* for replication target relation "public.tbl" in transaction \d+, finished at ([[:xdigit:]]+\/[[:xdigit:]]+)/m + qr/conflict detected on relation "public.tbl".*\n.*DETAIL:.* Could not apply remote change.*\n.*Key already exists in unique index "tbl_pkey", modified by .*origin.* in transaction \d+ at .*: \(key .*, local row .*\n.*CONTEXT:.* for replication target relation "public.tbl" in transaction \d+, finished at ([[:xdigit:]]+\/[[:xdigit:]]+)/m or die "could not get error-LSN"; my $lsn = $1; diff --git a/src/test/subscription/t/030_origin.pl b/src/test/subscription/t/030_origin.pl index 5076ebe609b..1d97889d49e 100644 --- a/src/test/subscription/t/030_origin.pl +++ b/src/test/subscription/t/030_origin.pl @@ -163,7 +163,7 @@ is($result, qq(32), 'The node_A data replicated to node_B'); $node_C->safe_psql('postgres', "UPDATE tab SET a = 33 WHERE a = 32;"); $node_B->wait_for_log( - qr/conflict detected on relation "public.tab": conflict=update_origin_differs.*\n.*DETAIL:.* Updating the row that was modified by a different origin ".*" in transaction [0-9]+ at .*: local row \(32\), remote row \(33\), replica identity \(a\)=\(32\)./ + qr/conflict detected on relation "public.tab": conflict=update_origin_differs.*\n.*DETAIL:.* Updating the row that was modified by a different origin ".*" in transaction [0-9]+ at .*: \(local row \(32\), remote row \(33\), replica identity \(a\)=\(32\)\)./ ); $node_B->safe_psql('postgres', "DELETE FROM tab;"); @@ -179,7 +179,7 @@ is($result, qq(33), 'The node_A data replicated to node_B'); $node_C->safe_psql('postgres', "DELETE FROM tab WHERE a = 33;"); $node_B->wait_for_log( - qr/conflict detected on relation "public.tab": conflict=delete_origin_differs.*\n.*DETAIL:.* Deleting the row that was modified by a different origin ".*" in transaction [0-9]+ at .*: local row \(33\), replica identity \(a\)=\(33\).*/ + qr/conflict detected on relation "public.tab": conflict=delete_origin_differs.*\n.*DETAIL:.* Deleting the row that was modified by a different origin ".*" in transaction [0-9]+ at .*: \(local row \(33\), replica identity \(a\)=\(33\)\).*/ ); # The remaining tests no longer test conflict detection. diff --git a/src/test/subscription/t/035_conflicts.pl b/src/test/subscription/t/035_conflicts.pl index 426ad74cf33..89a890ad3e9 100644 --- a/src/test/subscription/t/035_conflicts.pl +++ b/src/test/subscription/t/035_conflicts.pl @@ -78,10 +78,10 @@ $node_publisher->safe_psql('postgres', # Confirm that this causes an error on the subscriber $node_subscriber->wait_for_log( qr/conflict detected on relation \"public.conf_tab\": conflict=multiple_unique_conflicts.* -.*Could not apply remote change: remote row \(2, 3, 4\).* -.*Key already exists in unique index \"conf_tab_pkey\", modified in transaction .*: key \(a\)=\(2\), local row \(2, 2, 2\).* -.*Key already exists in unique index \"conf_tab_b_key\", modified in transaction .*: key \(b\)=\(3\), local row \(3, 3, 3\).* -.*Key already exists in unique index \"conf_tab_c_key\", modified in transaction .*: key \(c\)=\(4\), local row \(4, 4, 4\)./, +.*Could not apply remote change: \(remote row \(2, 3, 4\)\).* +.*Key already exists in unique index \"conf_tab_pkey\", modified in transaction .*: \(key \(a\)=\(2\), local row \(2, 2, 2\)\).* +.*Key already exists in unique index \"conf_tab_b_key\", modified in transaction .*: \(key \(b\)=\(3\), local row \(3, 3, 3\)\).* +.*Key already exists in unique index \"conf_tab_c_key\", modified in transaction .*: \(key \(c\)=\(4\), local row \(4, 4, 4\)\)./, $log_offset); pass('multiple_unique_conflicts detected during insert'); @@ -108,10 +108,10 @@ $node_publisher->safe_psql('postgres', # Confirm that this causes an error on the subscriber $node_subscriber->wait_for_log( qr/conflict detected on relation \"public.conf_tab\": conflict=multiple_unique_conflicts.* -.*Could not apply remote change: remote row \(6, 7, 8\), replica identity \(a\)=\(5\).* -.*Key already exists in unique index \"conf_tab_pkey\", modified in transaction .*: key \(a\)=\(6\), local row \(6, 6, 6\).* -.*Key already exists in unique index \"conf_tab_b_key\", modified in transaction .*: key \(b\)=\(7\), local row \(7, 7, 7\).* -.*Key already exists in unique index \"conf_tab_c_key\", modified in transaction .*: key \(c\)=\(8\), local row \(8, 8, 8\)./, +.*Could not apply remote change: \(remote row \(6, 7, 8\), replica identity \(a\)=\(5\)\).* +.*Key already exists in unique index \"conf_tab_pkey\", modified in transaction .*: \(key \(a\)=\(6\), local row \(6, 6, 6\)\).* +.*Key already exists in unique index \"conf_tab_b_key\", modified in transaction .*: \(key \(b\)=\(7\), local row \(7, 7, 7\)\).* +.*Key already exists in unique index \"conf_tab_c_key\", modified in transaction .*: \(key \(c\)=\(8\), local row \(8, 8, 8\)\)./, $log_offset); pass('multiple_unique_conflicts detected during update'); @@ -134,9 +134,9 @@ $node_publisher->safe_psql('postgres', $node_subscriber->wait_for_log( qr/conflict detected on relation \"public.conf_tab_2_p1\": conflict=multiple_unique_conflicts.* -.*Could not apply remote change: remote row \(55, 2, 3\).* -.*Key already exists in unique index \"conf_tab_2_p1_pkey\", modified in transaction .*: key \(a\)=\(55\), local row \(55, 2, 3\).* -.*Key already exists in unique index \"conf_tab_2_p1_a_b_key\", modified in transaction .*: key \(a, b\)=\(55, 2\), local row \(55, 2, 3\)./, +.*Could not apply remote change: \(remote row \(55, 2, 3\)\).* +.*Key already exists in unique index \"conf_tab_2_p1_pkey\", modified in transaction .*: \(key \(a\)=\(55\), local row \(55, 2, 3\)\).* +.*Key already exists in unique index \"conf_tab_2_p1_a_b_key\", modified in transaction .*: \(key \(a, b\)=\(55, 2\), local row \(55, 2, 3\)\)./, $log_offset); pass('multiple_unique_conflicts detected on a leaf partition during insert'); @@ -314,7 +314,7 @@ my $logfile = slurp_file($node_B->logfile(), $log_location); like( $logfile, qr/conflict detected on relation "public.tab": conflict=delete_origin_differs.* -.*DETAIL:.* Deleting the row that was modified locally in transaction [0-9]+ at .*: local row \(1, 3\), replica identity \(a\)=\(1\)./, +.*DETAIL:.* Deleting the row that was modified locally in transaction [0-9]+ at .*: \(local row \(1, 3\), replica identity \(a\)=\(1\)\)./, 'delete target row was modified in tab'); $log_location = -s $node_A->logfile; @@ -327,7 +327,7 @@ $logfile = slurp_file($node_A->logfile(), $log_location); like( $logfile, qr/conflict detected on relation "public.tab": conflict=update_deleted.* -.*DETAIL:.* Could not find the row to be updated: remote row \(1, 3\), replica identity \(a\)=\(1\). +.*DETAIL:.* Could not find the row to be updated: \(remote row \(1, 3\), replica identity \(a\)=\(1\)\). .*The row to be updated was deleted locally in transaction [0-9]+ at .*/, 'update target row was deleted in tab'); @@ -375,7 +375,7 @@ $logfile = slurp_file($node_A->logfile(), $log_location); like( $logfile, qr/conflict detected on relation "public.tab": conflict=update_deleted.* -.*DETAIL:.* Could not find the row to be updated: remote row \(2, 4\), replica identity full \(2, 2\).* +.*DETAIL:.* Could not find the row to be updated: \(remote row \(2, 4\), replica identity full \(2, 2\)\).* .*The row to be updated was deleted locally in transaction [0-9]+ at .*/, 'update target row was deleted in tab'); @@ -534,7 +534,7 @@ if ($injection_points_supported != 0) like( $logfile, qr/conflict detected on relation "public.tab": conflict=update_deleted.* -.*DETAIL:.* Could not find the row to be updated: remote row \(1, 2\), replica identity full \(1, 1\).* +.*DETAIL:.* Could not find the row to be updated: \(remote row \(1, 2\), replica identity full \(1, 1\)\).* .*The row to be updated was deleted locally in transaction [0-9]+ at .*/, 'update target row was deleted in tab'); -- 2.43.0