From 99234e9ddc02f45eb122f83d49031e2c517d0af8 Mon Sep 17 00:00:00 2001 From: Peter Eisentraut Date: Mon, 25 Aug 2025 22:59:00 +0200 Subject: Message wording improvements Use "row" instead of "tuple" for user-facing information for logical replication conflicts. --- doc/src/sgml/logical-replication.sgml | 28 ++++++++++++++-------------- src/backend/executor/execReplication.c | 6 +++--- src/backend/replication/logical/conflict.c | 26 +++++++++++++------------- src/include/replication/conflict.h | 4 ++-- src/test/subscription/t/001_rep_changes.pl | 4 ++-- src/test/subscription/t/013_partition.pl | 8 ++++---- src/test/subscription/t/029_on_error.pl | 2 +- src/test/subscription/t/030_origin.pl | 4 ++-- src/test/subscription/t/035_conflicts.pl | 22 +++++++++++----------- 9 files changed, 52 insertions(+), 52 deletions(-) diff --git a/doc/src/sgml/logical-replication.sgml b/doc/src/sgml/logical-replication.sgml index 0ac29928f17..9ccd5ec5006 100644 --- a/doc/src/sgml/logical-replication.sgml +++ b/doc/src/sgml/logical-replication.sgml @@ -1824,7 +1824,7 @@ Publications: update_missing - The tuple to be updated was not found. The update will simply be + The row to be updated was not found. The update will simply be skipped in this scenario. @@ -1845,7 +1845,7 @@ Publications: delete_missing - The tuple to be deleted was not found. The delete will simply be + The row to be deleted was not found. The delete will simply be skipped in this scenario. @@ -1879,8 +1879,8 @@ DETAIL: detailed_explanation. where detail_values is one of: Key (column_name , ...)=(column_value , ...) - existing local tuple (column_name , ...)=(column_value , ...) - remote tuple (column_name , ...)=(column_value , ...) + existing local row (column_name , ...)=(column_value , ...) + remote row (column_name , ...)=(column_value , ...) replica identity {(column_name , ...)=(column_value , ...) | full (column_name , ...)=(column_value , ...)} @@ -1914,32 +1914,32 @@ DETAIL: detailed_explanation. detailed_explanation includes the origin, transaction ID, and commit timestamp of the transaction that - modified the existing local tuple, if available. + modified the existing local row, if available. The Key section includes the key values of the local - tuple that violated a unique constraint for + row that violated a unique constraint for insert_exists, update_exists or multiple_unique_conflicts conflicts. - The existing local tuple section includes the local - tuple if its origin differs from the remote tuple for + The existing local row section includes the local + row if its origin differs from the remote row for update_origin_differs or delete_origin_differs - conflicts, or if the key value conflicts with the remote tuple for + conflicts, or if the key value conflicts with the remote row for insert_exists, update_exists or multiple_unique_conflicts conflicts. - The remote tuple section includes the new tuple from + The remote row section includes the new row from the remote insert or update operation that caused the conflict. Note that - for an update operation, the column value of the new tuple will be null + for an update operation, the column value of the new row will be null if the value is unchanged and toasted. @@ -1947,7 +1947,7 @@ DETAIL: detailed_explanation. The replica identity section includes the replica identity key values that were used to search for the existing local - tuple to be updated or deleted. This may include the full tuple value + row to be updated or deleted. This may include the full row value if the local relation is marked with REPLICA IDENTITY FULL. @@ -1955,7 +1955,7 @@ DETAIL: detailed_explanation. column_name is the column name. - For existing local tuple, remote tuple, + For existing local row, remote row, and replica identity full cases, column names are logged only if the user lacks the privilege to access all columns of the table. If column names are present, they appear in the same order @@ -2012,7 +2012,7 @@ DETAIL: detailed_explanation. ERROR: conflict detected on relation "public.test": conflict=insert_exists DETAIL: Key already exists in unique index "t_pkey", which was modified locally in transaction 740 at 2024-06-26 10:47:04.727375+08. -Key (c)=(1); existing local tuple (1, 'local'); remote tuple (1, 'remote'). +Key (c)=(1); existing local row (1, 'local'); remote row (1, 'remote'). CONTEXT: processing remote data for replication origin "pg_16395" during "INSERT" for replication target relation "public.test" in transaction 725 finished at 0/014C0378 The LSN of the transaction that contains the change violating the constraint and diff --git a/src/backend/executor/execReplication.c b/src/backend/executor/execReplication.c index da0cbf41d6f..b409d4ecbf5 100644 --- a/src/backend/executor/execReplication.c +++ b/src/backend/executor/execReplication.c @@ -852,10 +852,10 @@ ExecSimpleRelationInsert(ResultRelInfo *resultRelInfo, conflictindexes, false); /* - * Checks the conflict indexes to fetch the conflicting local tuple - * and reports the conflict. We perform this check here, instead of + * Checks the conflict indexes to fetch the conflicting local row and + * reports the conflict. We perform this check here, instead of * performing an additional index scan before the actual insertion and - * reporting the conflict if any conflicting tuples are found. This is + * reporting the conflict if any conflicting rows are found. This is * to avoid the overhead of executing the extra scan for each INSERT * operation, even when no conflict arises, which could introduce * significant overhead to replication, particularly in cases where diff --git a/src/backend/replication/logical/conflict.c b/src/backend/replication/logical/conflict.c index 2fd3e8bbda5..16695592265 100644 --- a/src/backend/replication/logical/conflict.c +++ b/src/backend/replication/logical/conflict.c @@ -55,7 +55,7 @@ static char *build_index_value_desc(EState *estate, Relation localrel, /* * Get the xmin and commit timestamp data (origin and timestamp) associated - * with the provided local tuple. + * with the provided local row. * * Return true if the commit timestamp data was found, false otherwise. */ @@ -89,12 +89,12 @@ GetTupleTransactionInfo(TupleTableSlot *localslot, TransactionId *xmin, * This function is used to report a conflict while applying replication * changes. * - * 'searchslot' should contain the tuple used to search the local tuple to be + * 'searchslot' should contain the tuple used to search the local row to be * updated or deleted. * * 'remoteslot' should contain the remote new tuple, if any. * - * conflicttuples is a list of local tuples that caused the conflict and the + * conflicttuples is a list of local rows that caused the conflict and the * conflict related information. See ConflictTupleInfo. * * The caller must ensure that all the indexes passed in ConflictTupleInfo are @@ -191,9 +191,9 @@ errcode_apply_conflict(ConflictType type) * * The DETAIL line comprises of two parts: * 1. Explanation of the conflict type, including the origin and commit - * timestamp of the existing local tuple. - * 2. Display of conflicting key, existing local tuple, remote new tuple, and - * replica identity columns, if any. The remote old tuple is excluded as its + * timestamp of the existing local row. + * 2. Display of conflicting key, existing local row, remote new row, and + * replica identity columns, if any. The remote old row is excluded as its * information is covered in the replica identity columns. */ static void @@ -313,7 +313,7 @@ errdetail_apply_conflict(EState *estate, ResultRelInfo *relinfo, localslot, remoteslot, indexoid); /* - * Next, append the key values, existing local tuple, remote tuple and + * Next, append the key values, existing local row, remote row, and * replica identity columns after the message. */ if (val_desc) @@ -331,7 +331,7 @@ errdetail_apply_conflict(EState *estate, ResultRelInfo *relinfo, /* * Helper function to build the additional details for conflicting key, - * existing local tuple, remote tuple, and replica identity columns. + * existing local row, remote row, and replica identity columns. * * If the return value is NULL, it indicates that the current user lacks * permissions to view the columns involved. @@ -373,7 +373,7 @@ build_tuple_value_details(EState *estate, ResultRelInfo *relinfo, { /* * The 'modifiedCols' only applies to the new tuple, hence we pass - * NULL for the existing local tuple. + * NULL for the existing local row. */ desc = ExecBuildSlotValueDescription(relid, localslot, tupdesc, NULL, 64); @@ -383,12 +383,12 @@ build_tuple_value_details(EState *estate, ResultRelInfo *relinfo, if (tuple_value.len > 0) { appendStringInfoString(&tuple_value, "; "); - appendStringInfo(&tuple_value, _("existing local tuple %s"), + appendStringInfo(&tuple_value, _("existing local row %s"), desc); } else { - appendStringInfo(&tuple_value, _("Existing local tuple %s"), + appendStringInfo(&tuple_value, _("Existing local row %s"), desc); } } @@ -415,11 +415,11 @@ build_tuple_value_details(EState *estate, ResultRelInfo *relinfo, if (tuple_value.len > 0) { appendStringInfoString(&tuple_value, "; "); - appendStringInfo(&tuple_value, _("remote tuple %s"), desc); + appendStringInfo(&tuple_value, _("remote row %s"), desc); } else { - appendStringInfo(&tuple_value, _("Remote tuple %s"), desc); + appendStringInfo(&tuple_value, _("Remote row %s"), desc); } } } diff --git a/src/include/replication/conflict.h b/src/include/replication/conflict.h index ff3cb8416ec..e516caa5c73 100644 --- a/src/include/replication/conflict.h +++ b/src/include/replication/conflict.h @@ -57,7 +57,7 @@ typedef enum #define CONFLICT_NUM_TYPES (CT_MULTIPLE_UNIQUE_CONFLICTS + 1) /* - * Information for the existing local tuple that caused the conflict. + * Information for the existing local row that caused the conflict. */ typedef struct ConflictTupleInfo { @@ -69,7 +69,7 @@ typedef struct ConflictTupleInfo * the conflict */ RepOriginId origin; /* origin identifier of the modification */ TimestampTz ts; /* timestamp of when the modification on the - * conflicting local tuple occurred */ + * conflicting local row occurred */ } ConflictTupleInfo; extern bool GetTupleTransactionInfo(TupleTableSlot *localslot, diff --git a/src/test/subscription/t/001_rep_changes.pl b/src/test/subscription/t/001_rep_changes.pl index 916fdb48b3b..ca55d8df50d 100644 --- a/src/test/subscription/t/001_rep_changes.pl +++ b/src/test/subscription/t/001_rep_changes.pl @@ -365,10 +365,10 @@ $node_publisher->wait_for_catchup('tap_sub'); my $logfile = slurp_file($node_subscriber->logfile, $log_location); ok( $logfile =~ - qr/conflict detected on relation "public.tab_full_pk": conflict=update_missing.*\n.*DETAIL:.* Could not find the row to be updated.*\n.*Remote tuple \(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.*\n.*Remote row \(1, quux\); replica identity \(a\)=\(1\)/m, 'update target row is missing'); ok( $logfile =~ - qr/conflict detected on relation "public.tab_full": conflict=update_missing.*\n.*DETAIL:.* Could not find the row to be updated.*\n.*Remote tuple \(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.*\n.*Remote row \(26\); replica identity full \(25\)/m, 'update target row is missing'); ok( $logfile =~ qr/conflict detected on relation "public.tab_full_pk": conflict=delete_missing.*\n.*DETAIL:.* Could not find the row to be deleted.*\n.*Replica identity \(a\)=\(2\)/m, diff --git a/src/test/subscription/t/013_partition.pl b/src/test/subscription/t/013_partition.pl index 4f78dd48815..763a91e75a3 100644 --- a/src/test/subscription/t/013_partition.pl +++ b/src/test/subscription/t/013_partition.pl @@ -368,7 +368,7 @@ $node_publisher->wait_for_catchup('sub2'); my $logfile = slurp_file($node_subscriber1->logfile(), $log_location); ok( $logfile =~ - qr/conflict detected on relation "public.tab1_2_2": conflict=update_missing.*\n.*DETAIL:.* Could not find the row to be updated.*\n.*Remote tuple \(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.*\n.*Remote row \(null, 4, quux\); replica identity \(a\)=\(4\)/, 'update target row is missing in tab1_2_2'); ok( $logfile =~ qr/conflict detected on relation "public.tab1_1": conflict=delete_missing.*\n.*DETAIL:.* Could not find the row to be deleted.*\n.*Replica identity \(a\)=\(1\)/, @@ -781,7 +781,7 @@ $node_publisher->wait_for_catchup('sub2'); $logfile = slurp_file($node_subscriber1->logfile(), $log_location); ok( $logfile =~ - qr/conflict detected on relation "public.tab2_1": conflict=update_missing.*\n.*DETAIL:.* Could not find the row to be updated.*\n.*Remote tuple \(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.*\n.*Remote row \(pub_tab2, quux, 5\); replica identity \(a\)=\(5\)/, 'update target row is missing in tab2_1'); ok( $logfile =~ qr/conflict detected on relation "public.tab2_1": conflict=delete_missing.*\n.*DETAIL:.* Could not find the row to be deleted.*\n.*Replica identity \(a\)=\(1\)/, @@ -802,8 +802,8 @@ $node_publisher->wait_for_catchup('sub_viaroot'); $logfile = slurp_file($node_subscriber1->logfile(), $log_location); ok( $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 .*\n.*Existing local tuple \(yyy, null, 3\); remote tuple \(pub_tab2, quux, 3\); replica identity \(a\)=\(3\)/, - 'updating a tuple that was modified by a different origin'); + 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 .*\n.*Existing 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. $node_subscriber1->append_conf('postgresql.conf', diff --git a/src/test/subscription/t/029_on_error.pl b/src/test/subscription/t/029_on_error.pl index 243662e1240..b59d20599fd 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:.* Key already exists in unique index "tbl_pkey", modified by .*origin.* transaction \d+ at .*\n.*Key \(i\)=\(\d+\); existing local tuple .*; remote tuple .*\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:.* Key already exists in unique index "tbl_pkey", modified by .*origin.* transaction \d+ at .*\n.*Key \(i\)=\(\d+\); existing local row .*; remote 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 5b82848e5e6..ec6518ca010 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 .*\n.*Existing local tuple \(32\); remote tuple \(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 .*\n.*Existing 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 .*\n.*Existing local tuple \(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 .*\n.*Existing 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 36aeb14c563..6b4a9fb8815 100644 --- a/src/test/subscription/t/035_conflicts.pl +++ b/src/test/subscription/t/035_conflicts.pl @@ -79,11 +79,11 @@ $node_publisher->safe_psql('postgres', $node_subscriber->wait_for_log( qr/conflict detected on relation \"public.conf_tab\": conflict=multiple_unique_conflicts.* .*Key already exists in unique index \"conf_tab_pkey\".* -.*Key \(a\)=\(2\); existing local tuple \(2, 2, 2\); remote tuple \(2, 3, 4\).* +.*Key \(a\)=\(2\); existing local row \(2, 2, 2\); remote row \(2, 3, 4\).* .*Key already exists in unique index \"conf_tab_b_key\".* -.*Key \(b\)=\(3\); existing local tuple \(3, 3, 3\); remote tuple \(2, 3, 4\).* +.*Key \(b\)=\(3\); existing local row \(3, 3, 3\); remote row \(2, 3, 4\).* .*Key already exists in unique index \"conf_tab_c_key\".* -.*Key \(c\)=\(4\); existing local tuple \(4, 4, 4\); remote tuple \(2, 3, 4\)./, +.*Key \(c\)=\(4\); existing local row \(4, 4, 4\); remote row \(2, 3, 4\)./, $log_offset); pass('multiple_unique_conflicts detected during insert'); @@ -111,11 +111,11 @@ $node_publisher->safe_psql('postgres', $node_subscriber->wait_for_log( qr/conflict detected on relation \"public.conf_tab\": conflict=multiple_unique_conflicts.* .*Key already exists in unique index \"conf_tab_pkey\".* -.*Key \(a\)=\(6\); existing local tuple \(6, 6, 6\); remote tuple \(6, 7, 8\).* +.*Key \(a\)=\(6\); existing local row \(6, 6, 6\); remote row \(6, 7, 8\).* .*Key already exists in unique index \"conf_tab_b_key\".* -.*Key \(b\)=\(7\); existing local tuple \(7, 7, 7\); remote tuple \(6, 7, 8\).* +.*Key \(b\)=\(7\); existing local row \(7, 7, 7\); remote row \(6, 7, 8\).* .*Key already exists in unique index \"conf_tab_c_key\".* -.*Key \(c\)=\(8\); existing local tuple \(8, 8, 8\); remote tuple \(6, 7, 8\)./, +.*Key \(c\)=\(8\); existing local row \(8, 8, 8\); remote row \(6, 7, 8\)./, $log_offset); pass('multiple_unique_conflicts detected during update'); @@ -139,9 +139,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.* .*Key already exists in unique index \"conf_tab_2_p1_pkey\".* -.*Key \(a\)=\(55\); existing local tuple \(55, 2, 3\); remote tuple \(55, 2, 3\).* +.*Key \(a\)=\(55\); existing local row \(55, 2, 3\); remote row \(55, 2, 3\).* .*Key already exists in unique index \"conf_tab_2_p1_a_b_key\".* -.*Key \(a, b\)=\(55, 2\); existing local tuple \(55, 2, 3\); remote tuple \(55, 2, 3\)./, +.*Key \(a, b\)=\(55, 2\); existing local row \(55, 2, 3\); remote 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); ok( $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 .* -.*Existing local tuple \(1, 3\); replica identity \(a\)=\(1\)/, +.*Existing 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); 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 tuple \(1, 3\); replica identity \(a\)=\(1\)/, +.*Remote row \(1, 3\); replica identity \(a\)=\(1\)/, 'update target row was deleted in tab'); # Remember the next transaction ID to be assigned @@ -383,7 +383,7 @@ $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 tuple \(2, 4\); replica identity full \(2, 2\)/, +.*Remote row \(2, 4\); replica identity full \(2, 2\)/, 'update target row was deleted in tab'); ############################################################################### -- cgit v1.2.3