mirror of
https://github.com/postgres/postgres.git
synced 2026-01-26 09:41:40 +03:00
Improve errdetail for logical replication conflict messages.
This change enhances the clarity and usefulness of error detail messages generated during logical replication conflicts. The following improvements have been made: 1. Eliminate redundant output: Avoid printing duplicate remote row and replica identity values for the multiple_unique_conflicts conflict type. 2. Improve message structure: Append tuple values directly to the main error message, separated by a colon (:), for better readability. 3. Simplify local row terminology: Remove the word 'existing' when referring to the local row, as this is already implied by context. 4. General code refinements: Apply miscellaneous code cleanups to improve how conflict detail messages are constructed and formatted. Author: Hayato Kuroda <kuroda.hayato@fujitsu.com> Reviewed-by: Shveta Malik <shveta.malik@gmail.com> Reviewed-by: Amit Kapila <amit.kapila16@gmail.com> Reviewed-by: Masahiko Sawada <sawada.mshk@gmail.com> Reviewed-by: Zhijie Hou <houzj.fnst@fujitsu.com> Discussion: https://postgr.es/m/CAHut+Psgkwy5-yGRJC15izecySGGysrbCszv_z93ess8XtCDOQ@mail.gmail.com
This commit is contained in:
@@ -2121,13 +2121,12 @@ Publications:
|
||||
The log format for logical replication conflicts is as follows:
|
||||
<synopsis>
|
||||
LOG: conflict detected on relation "<replaceable>schemaname</replaceable>.<replaceable>tablename</replaceable>": conflict=<replaceable>conflict_type</replaceable>
|
||||
DETAIL: <replaceable class="parameter">detailed_explanation</replaceable>.
|
||||
{<replaceable class="parameter">detail_values</replaceable> [; ... ]}.
|
||||
DETAIL: <replaceable class="parameter">detailed_explanation</replaceable>[: <replaceable class="parameter">detail_values</replaceable> [, ... ]].
|
||||
|
||||
<phrase>where <replaceable class="parameter">detail_values</replaceable> is one of:</phrase>
|
||||
|
||||
<literal>Key</literal> (<replaceable>column_name</replaceable> <optional>, ...</optional>)=(<replaceable>column_value</replaceable> <optional>, ...</optional>)
|
||||
<literal>existing local row</literal> <optional>(<replaceable>column_name</replaceable> <optional>, ...</optional>)=</optional>(<replaceable>column_value</replaceable> <optional>, ...</optional>)
|
||||
<literal>key</literal> (<replaceable>column_name</replaceable> <optional>, ...</optional>)=(<replaceable>column_value</replaceable> <optional>, ...</optional>)
|
||||
<literal>local row</literal> <optional>(<replaceable>column_name</replaceable> <optional>, ...</optional>)=</optional>(<replaceable>column_value</replaceable> <optional>, ...</optional>)
|
||||
<literal>remote row</literal> <optional>(<replaceable>column_name</replaceable> <optional>, ...</optional>)=</optional>(<replaceable>column_value</replaceable> <optional>, ...</optional>)
|
||||
<literal>replica identity</literal> {(<replaceable>column_name</replaceable> <optional>, ...</optional>)=(<replaceable>column_value</replaceable> <optional>, ...</optional>) | full <optional>(<replaceable>column_name</replaceable> <optional>, ...</optional>)=</optional>(<replaceable>column_value</replaceable> <optional>, ...</optional>)}
|
||||
</synopsis>
|
||||
@@ -2162,12 +2161,12 @@ DETAIL: <replaceable class="parameter">detailed_explanation</replaceable>.
|
||||
<para>
|
||||
<replaceable class="parameter">detailed_explanation</replaceable> includes
|
||||
the origin, transaction ID, and commit timestamp of the transaction that
|
||||
modified the existing local row, if available.
|
||||
modified the local row, if available.
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
The <literal>Key</literal> section includes the key values of the local
|
||||
The <literal>key</literal> section includes the key values of the local
|
||||
row that violated a unique constraint for
|
||||
<literal>insert_exists</literal>, <literal>update_exists</literal> or
|
||||
<literal>multiple_unique_conflicts</literal> conflicts.
|
||||
@@ -2175,8 +2174,8 @@ DETAIL: <replaceable class="parameter">detailed_explanation</replaceable>.
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
The <literal>existing local row</literal> section includes the local
|
||||
row if its origin differs from the remote row for
|
||||
The <literal>local row</literal> section includes the local row if its
|
||||
origin differs from the remote row for
|
||||
<literal>update_origin_differs</literal> or <literal>delete_origin_differs</literal>
|
||||
conflicts, or if the key value conflicts with the remote row for
|
||||
<literal>insert_exists</literal>, <literal>update_exists</literal> or
|
||||
@@ -2203,8 +2202,8 @@ DETAIL: <replaceable class="parameter">detailed_explanation</replaceable>.
|
||||
<listitem>
|
||||
<para>
|
||||
<replaceable class="parameter">column_name</replaceable> is the column name.
|
||||
For <literal>existing local row</literal>, <literal>remote row</literal>,
|
||||
and <literal>replica identity full</literal> cases, column names are
|
||||
For <literal>local row</literal>, <literal>remote row</literal>, and
|
||||
<literal>replica identity full</literal> 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
|
||||
as the corresponding column values.
|
||||
@@ -2259,8 +2258,8 @@ DETAIL: <replaceable class="parameter">detailed_explanation</replaceable>.
|
||||
emit the following kind of message to the subscriber's server log:
|
||||
<screen>
|
||||
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 row (1, 'local'); remote row (1, 'remote').
|
||||
DETAIL: Could not apply remote change: remote row (1, 'remote').
|
||||
Key already exists in unique index "test_pkey", modified locally in transaction 800 at 2026-01-16 18:15:25.652759+09: key (c)=(1), local row (1, 'local').
|
||||
CONTEXT: processing remote data for replication origin "pg_16395" during "INSERT" for replication target relation "public.test" in transaction 725 finished at 0/014C0378
|
||||
</screen>
|
||||
The LSN of the transaction that contains the change violating the constraint and
|
||||
|
||||
@@ -44,12 +44,12 @@ static void errdetail_apply_conflict(EState *estate,
|
||||
Oid indexoid, TransactionId localxmin,
|
||||
RepOriginId localorigin,
|
||||
TimestampTz localts, StringInfo err_msg);
|
||||
static char *build_tuple_value_details(EState *estate, ResultRelInfo *relinfo,
|
||||
ConflictType type,
|
||||
TupleTableSlot *searchslot,
|
||||
TupleTableSlot *localslot,
|
||||
TupleTableSlot *remoteslot,
|
||||
Oid indexoid);
|
||||
static void get_tuple_desc(EState *estate, ResultRelInfo *relinfo,
|
||||
ConflictType type, char **key_desc,
|
||||
TupleTableSlot *searchslot, char **search_desc,
|
||||
TupleTableSlot *localslot, char **local_desc,
|
||||
TupleTableSlot *remoteslot, char **remote_desc,
|
||||
Oid indexoid);
|
||||
static char *build_index_value_desc(EState *estate, Relation localrel,
|
||||
TupleTableSlot *slot, Oid indexoid);
|
||||
|
||||
@@ -186,14 +186,66 @@ errcode_apply_conflict(ConflictType type)
|
||||
return 0; /* silence compiler warning */
|
||||
}
|
||||
|
||||
/*
|
||||
* Helper function to build the additional details for conflicting key,
|
||||
* local row, remote row, and replica identity columns.
|
||||
*/
|
||||
static void
|
||||
append_tuple_value_detail(StringInfo buf, List *tuple_values,
|
||||
bool need_newline)
|
||||
{
|
||||
bool first = true;
|
||||
|
||||
Assert(buf != NULL && tuple_values != NIL);
|
||||
|
||||
foreach_ptr(char, tuple_value, tuple_values)
|
||||
{
|
||||
/*
|
||||
* Skip if the value is NULL. This means the current user does not
|
||||
* have enough permissions to see all columns in the table. See
|
||||
* get_tuple_desc().
|
||||
*/
|
||||
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, _(", "));
|
||||
}
|
||||
|
||||
appendStringInfoString(buf, tuple_value);
|
||||
first = false;
|
||||
}
|
||||
|
||||
/* translator: This is the terminator of a conflict message */
|
||||
appendStringInfoString(buf, _("."));
|
||||
|
||||
if (need_newline)
|
||||
appendStringInfoChar(buf, '\n');
|
||||
}
|
||||
|
||||
/*
|
||||
* Add an errdetail() line showing conflict detail.
|
||||
*
|
||||
* The DETAIL line comprises of two parts:
|
||||
* 1. Explanation of the conflict type, including the origin and commit
|
||||
* 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
|
||||
* timestamp of the local row.
|
||||
* 2. Display of conflicting key, 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
|
||||
@@ -205,12 +257,22 @@ errdetail_apply_conflict(EState *estate, ResultRelInfo *relinfo,
|
||||
StringInfo err_msg)
|
||||
{
|
||||
StringInfoData err_detail;
|
||||
char *val_desc;
|
||||
char *origin_name;
|
||||
char *key_desc = NULL;
|
||||
char *local_desc = NULL;
|
||||
char *remote_desc = NULL;
|
||||
char *search_desc = NULL;
|
||||
|
||||
/* Get key, replica identity, remote, and local value data */
|
||||
get_tuple_desc(estate, relinfo, type, &key_desc,
|
||||
localslot, &local_desc,
|
||||
remoteslot, &remote_desc,
|
||||
searchslot, &search_desc,
|
||||
indexoid);
|
||||
|
||||
initStringInfo(&err_detail);
|
||||
|
||||
/* First, construct a detailed message describing the type of conflict */
|
||||
/* Construct a detailed message describing the type of conflict */
|
||||
switch (type)
|
||||
{
|
||||
case CT_INSERT_EXISTS:
|
||||
@@ -219,14 +281,23 @@ errdetail_apply_conflict(EState *estate, ResultRelInfo *relinfo,
|
||||
Assert(OidIsValid(indexoid) &&
|
||||
CheckRelationOidLockedByMe(indexoid, RowExclusiveLock, true));
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
if (localts)
|
||||
{
|
||||
if (localorigin == InvalidRepOriginId)
|
||||
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"),
|
||||
get_rel_name(indexoid),
|
||||
localxmin, timestamptz_to_str(localts));
|
||||
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"),
|
||||
get_rel_name(indexoid), origin_name,
|
||||
localxmin, timestamptz_to_str(localts));
|
||||
|
||||
@@ -238,87 +309,103 @@ 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"),
|
||||
get_rel_name(indexoid),
|
||||
localxmin, timestamptz_to_str(localts));
|
||||
}
|
||||
else
|
||||
appendStringInfo(&err_detail, _("Key already exists in unique index \"%s\", modified in transaction %u."),
|
||||
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);
|
||||
|
||||
break;
|
||||
|
||||
case CT_UPDATE_ORIGIN_DIFFERS:
|
||||
if (localorigin == InvalidRepOriginId)
|
||||
appendStringInfo(&err_detail, _("Updating the row that was modified locally in transaction %u at %s."),
|
||||
appendStringInfo(&err_detail, _("Updating the row that was modified locally in transaction %u at %s"),
|
||||
localxmin, timestamptz_to_str(localts));
|
||||
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."),
|
||||
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));
|
||||
|
||||
/* 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."),
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
if (localts)
|
||||
{
|
||||
if (localorigin == InvalidRepOriginId)
|
||||
appendStringInfo(&err_detail, _("The row to be updated was deleted locally in transaction %u at %s."),
|
||||
appendStringInfo(&err_detail, _("The row to be updated was deleted locally in transaction %u at %s"),
|
||||
localxmin, timestamptz_to_str(localts));
|
||||
else if (replorigin_by_oid(localorigin, true, &origin_name))
|
||||
appendStringInfo(&err_detail, _("The row to be updated was deleted by a different origin \"%s\" in transaction %u at %s."),
|
||||
appendStringInfo(&err_detail, _("The row to be updated was deleted by a different origin \"%s\" in transaction %u at %s"),
|
||||
origin_name, localxmin, timestamptz_to_str(localts));
|
||||
|
||||
/* The origin that modified this row has been removed. */
|
||||
else
|
||||
appendStringInfo(&err_detail, _("The row to be updated was deleted by a non-existent origin in transaction %u at %s."),
|
||||
appendStringInfo(&err_detail, _("The row to be updated was deleted by a non-existent origin in transaction %u at %s"),
|
||||
localxmin, timestamptz_to_str(localts));
|
||||
}
|
||||
else
|
||||
appendStringInfo(&err_detail, _("The row to be updated was deleted."));
|
||||
appendStringInfo(&err_detail, _("The row to be updated was deleted"));
|
||||
|
||||
break;
|
||||
|
||||
case CT_UPDATE_MISSING:
|
||||
appendStringInfoString(&err_detail, _("Could not find the row to be updated."));
|
||||
appendStringInfoString(&err_detail, _("Could not find the row to be updated"));
|
||||
|
||||
append_tuple_value_detail(&err_detail,
|
||||
list_make2(remote_desc, search_desc),
|
||||
false);
|
||||
|
||||
break;
|
||||
|
||||
case CT_DELETE_ORIGIN_DIFFERS:
|
||||
if (localorigin == InvalidRepOriginId)
|
||||
appendStringInfo(&err_detail, _("Deleting the row that was modified locally in transaction %u at %s."),
|
||||
appendStringInfo(&err_detail, _("Deleting the row that was modified locally in transaction %u at %s"),
|
||||
localxmin, timestamptz_to_str(localts));
|
||||
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."),
|
||||
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));
|
||||
|
||||
/* 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."),
|
||||
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);
|
||||
|
||||
break;
|
||||
|
||||
case CT_DELETE_MISSING:
|
||||
appendStringInfoString(&err_detail, _("Could not find the row to be deleted."));
|
||||
appendStringInfoString(&err_detail, _("Could not find the row to be deleted"));
|
||||
|
||||
append_tuple_value_detail(&err_detail,
|
||||
list_make1(search_desc), false);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
Assert(err_detail.len > 0);
|
||||
|
||||
val_desc = build_tuple_value_details(estate, relinfo, type, searchslot,
|
||||
localslot, remoteslot, indexoid);
|
||||
|
||||
/*
|
||||
* Next, append the key values, existing local row, remote row, and
|
||||
* replica identity columns after the message.
|
||||
*/
|
||||
if (val_desc)
|
||||
appendStringInfo(&err_detail, "\n%s", val_desc);
|
||||
|
||||
/*
|
||||
* Insert a blank line to visually separate the new detail line from the
|
||||
* existing ones.
|
||||
@@ -330,29 +417,27 @@ errdetail_apply_conflict(EState *estate, ResultRelInfo *relinfo,
|
||||
}
|
||||
|
||||
/*
|
||||
* Helper function to build the additional details for conflicting key,
|
||||
* existing local row, remote row, and replica identity columns.
|
||||
* Extract conflicting key, local row, remote row, and replica identity
|
||||
* columns. Results are set at xxx_desc.
|
||||
*
|
||||
* If the return value is NULL, it indicates that the current user lacks
|
||||
* permissions to view the columns involved.
|
||||
* If the output is NULL, it indicates that the current user lacks permissions
|
||||
* to view the columns involved.
|
||||
*/
|
||||
static char *
|
||||
build_tuple_value_details(EState *estate, ResultRelInfo *relinfo,
|
||||
ConflictType type,
|
||||
TupleTableSlot *searchslot,
|
||||
TupleTableSlot *localslot,
|
||||
TupleTableSlot *remoteslot,
|
||||
Oid indexoid)
|
||||
static void
|
||||
get_tuple_desc(EState *estate, ResultRelInfo *relinfo, ConflictType type,
|
||||
char **key_desc,
|
||||
TupleTableSlot *localslot, char **local_desc,
|
||||
TupleTableSlot *remoteslot, char **remote_desc,
|
||||
TupleTableSlot *searchslot, char **search_desc,
|
||||
Oid indexoid)
|
||||
{
|
||||
Relation localrel = relinfo->ri_RelationDesc;
|
||||
Oid relid = RelationGetRelid(localrel);
|
||||
TupleDesc tupdesc = RelationGetDescr(localrel);
|
||||
StringInfoData tuple_value;
|
||||
char *desc = NULL;
|
||||
|
||||
Assert(searchslot || localslot || remoteslot);
|
||||
|
||||
initStringInfo(&tuple_value);
|
||||
Assert((localslot && local_desc) || (remoteslot && remote_desc) ||
|
||||
(searchslot && search_desc));
|
||||
|
||||
/*
|
||||
* Report the conflicting key values in the case of a unique constraint
|
||||
@@ -363,35 +448,24 @@ build_tuple_value_details(EState *estate, ResultRelInfo *relinfo,
|
||||
{
|
||||
Assert(OidIsValid(indexoid) && localslot);
|
||||
|
||||
desc = build_index_value_desc(estate, localrel, localslot, indexoid);
|
||||
desc = build_index_value_desc(estate, localrel, localslot,
|
||||
indexoid);
|
||||
|
||||
if (desc)
|
||||
appendStringInfo(&tuple_value, _("Key %s"), desc);
|
||||
*key_desc = psprintf(_("key %s"), desc);
|
||||
}
|
||||
|
||||
if (localslot)
|
||||
{
|
||||
/*
|
||||
* The 'modifiedCols' only applies to the new tuple, hence we pass
|
||||
* NULL for the existing local row.
|
||||
* NULL for the local row.
|
||||
*/
|
||||
desc = ExecBuildSlotValueDescription(relid, localslot, tupdesc,
|
||||
NULL, 64);
|
||||
|
||||
if (desc)
|
||||
{
|
||||
if (tuple_value.len > 0)
|
||||
{
|
||||
appendStringInfoString(&tuple_value, "; ");
|
||||
appendStringInfo(&tuple_value, _("existing local row %s"),
|
||||
desc);
|
||||
}
|
||||
else
|
||||
{
|
||||
appendStringInfo(&tuple_value, _("Existing local row %s"),
|
||||
desc);
|
||||
}
|
||||
}
|
||||
*local_desc = psprintf(_("local row %s"), desc);
|
||||
}
|
||||
|
||||
if (remoteslot)
|
||||
@@ -407,21 +481,12 @@ build_tuple_value_details(EState *estate, ResultRelInfo *relinfo,
|
||||
*/
|
||||
modifiedCols = bms_union(ExecGetInsertedCols(relinfo, estate),
|
||||
ExecGetUpdatedCols(relinfo, estate));
|
||||
desc = ExecBuildSlotValueDescription(relid, remoteslot, tupdesc,
|
||||
modifiedCols, 64);
|
||||
desc = ExecBuildSlotValueDescription(relid, remoteslot,
|
||||
tupdesc, modifiedCols,
|
||||
64);
|
||||
|
||||
if (desc)
|
||||
{
|
||||
if (tuple_value.len > 0)
|
||||
{
|
||||
appendStringInfoString(&tuple_value, "; ");
|
||||
appendStringInfo(&tuple_value, _("remote row %s"), desc);
|
||||
}
|
||||
else
|
||||
{
|
||||
appendStringInfo(&tuple_value, _("Remote row %s"), desc);
|
||||
}
|
||||
}
|
||||
*remote_desc = psprintf(_("remote row %s"), desc);
|
||||
}
|
||||
|
||||
if (searchslot)
|
||||
@@ -449,27 +514,12 @@ build_tuple_value_details(EState *estate, ResultRelInfo *relinfo,
|
||||
|
||||
if (desc)
|
||||
{
|
||||
if (tuple_value.len > 0)
|
||||
{
|
||||
appendStringInfoString(&tuple_value, "; ");
|
||||
appendStringInfo(&tuple_value, OidIsValid(replica_index)
|
||||
? _("replica identity %s")
|
||||
: _("replica identity full %s"), desc);
|
||||
}
|
||||
if (OidIsValid(replica_index))
|
||||
*search_desc = psprintf(_("replica identity %s"), desc);
|
||||
else
|
||||
{
|
||||
appendStringInfo(&tuple_value, OidIsValid(replica_index)
|
||||
? _("Replica identity %s")
|
||||
: _("Replica identity full %s"), desc);
|
||||
}
|
||||
*search_desc = psprintf(_("replica identity full %s"), desc);
|
||||
}
|
||||
}
|
||||
|
||||
if (tuple_value.len == 0)
|
||||
return NULL;
|
||||
|
||||
appendStringInfoChar(&tuple_value, '.');
|
||||
return tuple_value.data;
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -64,7 +64,7 @@ typedef enum
|
||||
#define CONFLICT_NUM_TYPES (CT_MULTIPLE_UNIQUE_CONFLICTS + 1)
|
||||
|
||||
/*
|
||||
* Information for the existing local row that caused the conflict.
|
||||
* Information for the local row that caused the conflict.
|
||||
*/
|
||||
typedef struct ConflictTupleInfo
|
||||
{
|
||||
|
||||
@@ -366,15 +366,15 @@ $node_publisher->wait_for_catchup('tap_sub');
|
||||
my $logfile = slurp_file($node_subscriber->logfile, $log_location);
|
||||
like(
|
||||
$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 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.*\n.*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.*\n.*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',
|
||||
|
||||
@@ -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.*\n.*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.*\n.*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.*\n.*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.*\n.*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.*\n.*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.*\n.*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 .*\n.*Existing 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.
|
||||
|
||||
@@ -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 row .*; remote 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;
|
||||
|
||||
|
||||
@@ -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 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 .*\n.*Existing 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.
|
||||
|
||||
@@ -78,12 +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.*
|
||||
.*Key already exists in unique index \"conf_tab_pkey\".*
|
||||
.*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 row \(3, 3, 3\); remote row \(2, 3, 4\).*
|
||||
.*Key already exists in unique index \"conf_tab_c_key\".*
|
||||
.*Key \(c\)=\(4\); existing local row \(4, 4, 4\); remote row \(2, 3, 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');
|
||||
@@ -110,12 +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.*
|
||||
.*Key already exists in unique index \"conf_tab_pkey\".*
|
||||
.*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 row \(7, 7, 7\); remote row \(6, 7, 8\).*
|
||||
.*Key already exists in unique index \"conf_tab_c_key\".*
|
||||
.*Key \(c\)=\(8\); existing local row \(8, 8, 8\); remote row \(6, 7, 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');
|
||||
@@ -138,10 +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.*
|
||||
.*Key already exists in unique index \"conf_tab_2_p1_pkey\".*
|
||||
.*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 row \(55, 2, 3\); remote 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');
|
||||
@@ -319,8 +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 .*
|
||||
.*Existing 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;
|
||||
@@ -333,8 +327,8 @@ $logfile = slurp_file($node_A->logfile(), $log_location);
|
||||
like(
|
||||
$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, 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');
|
||||
|
||||
# Remember the next transaction ID to be assigned
|
||||
@@ -381,8 +375,8 @@ $logfile = slurp_file($node_A->logfile(), $log_location);
|
||||
like(
|
||||
$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 \(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');
|
||||
|
||||
###############################################################################
|
||||
@@ -540,8 +534,8 @@ if ($injection_points_supported != 0)
|
||||
like(
|
||||
$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\)/,
|
||||
.*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');
|
||||
|
||||
# Remember the next transaction ID to be assigned
|
||||
|
||||
Reference in New Issue
Block a user