From c51e0cc7fe46934e681018b2c19bcb96708883cc Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
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

