Subject: Re: Simplify code building the LR conflict messages

From: Amit Kapila <[email-masked]>
Date: Wed, 7 Jan 2026 15:05:04 +0530

On Thu, Dec 25, 2025 at 2:40 AM Masahiko Sawada <[email-masked]> wrote:
>
> On Sun, Nov 30, 2025 at 10:13 PM Amit Kapila <[email-masked]> wrote:
> >
>
> Regarding the proposed message style:
>
> > The idea to split it as per your suggestion, and assuming we agree
> > that additional row details are required for conflict/resolution based
> > on my above explanation.
> >
> > LOG:  conflict (multiple_unique_conflicts) detected on relation
> > "public.conf_tab"
> > DETAIL:  Key already exists in unique index "conf_tab_pkey", modified
> > locally in transaction 764 at 2025-12-01 08:59:50.789608+05:30.
> >         Key (a)=(2); existing local row (2, 2, 2); remote row (2, 3, 4).
> > LOG:  conflict (multiple_unique_conflicts) detected on relation
> > "public.conf_tab"
> > DETAIL:  Key already exists in unique index "conf_tab_b_key", modified
> > locally in transaction 764 at 2025-12-01 08:59:50.789608+05:30.
> >         Key (b)=(3); existing local row (3, 3, 3); remote row (2, 3, 4).
> > …
> > ERROR:  stopping replication because of previously-logged errors
> > CONTEXT:  processing remote data for replication origin "pg_16394"
> > during message type "INSERT" for replication target relation
> > "public.conf_tab" in transaction 759, finished at 0/017A7380
> >
>
> The DETAIL message still seems to violate our message style guideline.
> For instance, "Key (a)=(2); existing local row (2, 2, 2); remote row
> (2, 3, 4)." is not a complete sentence.
>
> I personally find that the current approach (i.e., having multiple
> detail lines in DETAIL) is more understandable since it's clear for
> users why the apply worker exited. With the proposed approach, after
> checking the ERROR log, the user would need to collect all relevant
> (potentially straggled) LOG messages from the server logs.
>

Fair enough. Also, with the current approach, we don't need to repeat
the same LOG message (
conflict (multiple_unique_conflicts) detected on relation
"public.conf_tab") again and again even though we do similar things at
other places[1] (the STATEMENT is repeated). If we have to follow your
advice then I can think of following formats:

Format-1:
DETAIL:  Key (a)=(2) already exists in unique index "conf_tab_pkey",
modified locally in transaction 764 at 2025-12-01
08:59:50.789608+05:30: local row (2, 2, 2), remote row (2, 3, 4).
Key (b)=(3) already exists in unique index "conf_tab_b_key", modified
locally in transaction 764 at 2025-12-01 08:59:50.789608+05:30: local
row (3, 3, 3); remote row (2, 3, 4).
...

Format-2:
Key already exists in unique index "conf_tab_pkey",
modified locally in transaction 764 at 2025-12-01
08:59:50.789608+05:30: key (a)=(2), local row (2, 2, 2), remote row
(2, 3, 4).
Key already exists in unique index "conf_tab_b_key", modified
locally in transaction 764 at 2025-12-01 08:59:50.789608+05:30: key
(b)=(3), local row (3, 3, 3), remote row (2, 3, 4).

The difference between both formats is that in the first format key
values are displayed in beginning and in the second format, it is
displayed along with rows. Note, I have removed the existing word from
the existing local row as that seems implicit. BTW, do we need to use
full stop at the end of each line if we have multiple lines to display
as part of the DETAIL message. AFAICS, in similar messages [2] (in
regression tests), we don't use full stop.

The above proposal is somewhat related to existing message:
errdetail_internal("Registering more than maximum %u bytes allowed to
block %u: current %u bytes, adding %u bytes.",

[1]:
[client backend] STATEMENT:  drop table toast_test;
[client backend] LOG:  drop auto-cascades to type toast_test
[client backend] STATEMENT:  drop table toast_test;
[client backend] LOG:  drop auto-cascades to type toast_test[]
[client backend] STATEMENT:  drop table toast_test;
[client backend] LOG:  drop auto-cascades to default value for column
id of table toast_test
[client backend] STATEMENT:  drop table toast_test;
[client backend] LOG:  drop auto-cascades to constraint
toast_test_id_not_null on table toast_test

[2]:
ERROR:  role "regress_role_joe" cannot be dropped because some objects
depend on it
DETAIL:  privileges for parameter test_oat_hooks.user_var1
privileges for parameter test_oat_hooks.super_var1
privileges for parameter test_oat_hooks.user_var2
privileges for parameter test_oat_hooks.super_var2
privileges for parameter none.such
privileges for parameter another.bogus

> If we report multiple_unique_conflicts conflicts by writing a
> combination of multiple LOGs and one ERROR, probably we might want to
> approach this style also for other conflict types for better
> consistency.
>

makes sense, if we have any other such reports but I think the
multiple detail lines is used only for multiple_unique_conflicts type
of conlict.

> The conflict report for multiple_unique_conflicts looks redundant to
> me as it shows the whole remote row image multiple times even though
> it should be the same.
>

Yeah, I think it is mostly because apart from
multiple_unique_conflicts, it won't be repeated and that makes the
code easier to write in its current form. However, you have a valid
point and we should try to eliminate duplicate information for remote
row and replica identity. How about something like:

ERROR:  conflict (multiple_unique_conflicts) detected on relation
"public.conf_tab"
DETAIL: Remote row (2, 3, 4) could not be applied by using replica
identity (a)=(5).
        Key (a)=(2) already exists in unique index "conf_tab_pkey",
modified locally in transaction 764 at 2025-12-01
08:59:50.789608+05:30: existing local row (2, 2, 2).
        Key (b)=(3) already exists in unique index "conf_tab_b_key",
modified locally in transaction 799 at 2025-12-02
09:00:00.123456+05:30: existing local row (3, 3, 3).
        Key (c)=(4) already exists in unique index "conf_tab_c_key",
modified locally in transaction 764 at 2025-12-01
08:59:50.789608+05:30: existing local row (4, 4, 4).

And when replica identity is not applicable then we can output like:

ERROR:  conflict (multiple_unique_conflicts) detected on relation
"public.conf_tab"
DETAIL: Remote row (2, 3, 4) could not be applied.
        Key (a)=(2) already exists in unique index "conf_tab_pkey",
modified locally in transaction 764 at 2025-12-01
08:59:50.789608+05:30: existing local row (2, 2, 2).
        Key (b)=(3) already exists in unique index "conf_tab_b_key",
modified locally in transaction 799 at 2025-12-02
09:00:00.123456+05:30: existing local row (3, 3, 3).
        Key (c)=(4) already exists in unique index "conf_tab_c_key",
modified locally in transaction 764 at 2025-12-01
08:59:50.789608+05:30: existing local row (4, 4, 4).

-- 
With Regards,
Amit Kapila.

---

From: "Hayato Kuroda (Fujitsu)" <[email-masked]>
Date: Wed, 7 Jan 2026 10:07:11 +0000

Dear Amit, Sawada-san,

...

Basically they look good to me, but I prefer to clarify the column name for each
tuples at least once per one output. Like:

```
DETAIL:  Key (a)=(2) already ... local row (a, b, c) = (2, 2, 2), remote row [(a, b, c) = ](2, 3, 4).
```

Admin can easily follow what is the exact reason why it happened.
Also, the ordering of column can be different between instances, and it may cause
misunderstanding. Currently they would be re-ordered by the subscriber-side ones,
but reader may understand by the publisher-side ones.

Best regards,
Hayato Kuroda
FUJITSU LIMITED

---

From: Amit Kapila <[email-masked]>
Date: Thu, 8 Jan 2026 10:56:18 +0530

> but reader may understand by the publisher-side ones.

As shown upthread, in existing places where we display the entire row,
we don't use columns, so doesn't see why we need to be different here.
I think but we can display for RI columns.

-- 
With Regards,
Amit Kapila.

---

From: "Hayato Kuroda (Fujitsu)" <[email-masked]>
Date: Fri, 9 Jan 2026 13:37:16 +0000

Dear Amit,

> I think but we can display for RI columns.

Thanks for the suggestion. I've created the first draft based on the comment.

While considering and implementing, I found that worker sometimes miss to read
information for indexes and relations due to the missing permissions. Previous
style just appended key/row/replica identity information at the bottom thus it
had less impacts.
However, it needs some branches if we tried to integrate into complete statements
to avoid constructing sentences at run-time.

E.g., if we have complete information, the output can be like:

```
Key (a) = (1) already exists in unique index "tab_pkey", modified in transaction 777: local row (1, 1).
```

But if the worker cannot read the content of the index, the statement should be slightly different like:

```
Unique index "tab_pkey" rejects applying due to local row (1, 1), modified in transaction 77.
```

How do you feel? This patch may need idea to reduce lines.

Best regards,
Hayato Kuroda
FUJITSU LIMITED

---

From: shveta malik <[email-masked]>
Date: Tue, 13 Jan 2026 09:51:48 +0530

> How do you feel? This patch may need idea to reduce lines.

I haven't looked at the code yet, but some initial thoughts while
looking at the output:

1)
DETAIL:  Could not apply remote row (20, 10).
DETAIL:  Could not apply remote row (40, 200) by using replica
identity (i)=(20).

We generally "apply" in terms of insert, update, delete etc and not
rows. Do you think we shall have:
'Could not apply remote change (20, 10)..'

The most informative will be to say below, but since operation-type is
already mentioned in Context, mentioning it here might not be needed.
So we can say 'remote change'.

DETAIL: Could not apply remote INSERT for row (30, 10).
DETAIL: Could not apply remote UPDATE for row (40, 200) using replica
identity (i)=(20).

2)
LOG:  conflict update_origin_differs detected on relation "public.tab1"
DETAIL:  Remote row (20, 200) was applied but previously modified by
different origin.
Local row (20, 100) detected by replica identity (i)=(20) is being
updated, but it was previously modified locally in transaction 804 at
2026-01-12 14:20:28.346397+05:30.

Row embedded in the sentence may make it difficult to read when the
count of columns is huge.

Shall we make it similar to what we had earlier:
DETAIL:  Updating the row that was modified locally in transaction
<..> at <..>: local row (..), remote row (..), replica identity
(i)=(..).

Or close to newer version:
DETAIL:  The row is being updated, but was previously modified locally
in transaction <..> at <..>: local row (..), remote row (..), replica
identity (i)=(..).

3)
Comment 2 is valid for 'delete_origin_differs' as well. Both should
have exactly the same format.

4)
LOG:  conflict update_deleted detected on relation "public.tab1"
DETAIL:  Could not find remote row (10, 100) by using replica identity (i)=(10).

The remote-row is embedded here too. Shall it be:
DETAIL: Could not find the row by using replica identity (i) = (10):
remote row (10, 100).

Please note that replica-identity when set to FULl can also be huge,
so both RI and remote-row at the end makes sense.

thanks
Shveta

---

From: Amit Kapila <[email-masked]>
Date: Wed, 14 Jan 2026 15:20:25 +0530

> DETAIL: Could not apply remote UPDATE for row (40, 200) using replica

IIRC, the operation is already displayed in the context message. Here,
we can say: DETAIL: Could not apply remote change using replica
identity (i)=(20): remote row (40, 200).

And adjust other messages accordingly.

-- 
With Regards,
Amit Kapila.

---

From: "Hayato Kuroda (Fujitsu)" <[email-masked]>
Date: Wed, 14 Jan 2026 11:31:04 +0000

Dear Amit, Shveta,

> identity (i)=(20): remote row (40, 200).

Thanks for suggestions. I updated accordingly. Also, I found that local slot won't
be available in case of update_deleted, thus I removed from the code.
Current concern is that {update|delete}_origin_differ still construct the message
because there are three printable info. Can you suggest if you know better approaches?

In below I want to show some examples.

Case 1: multiple_unique_conflicts with UPDATE
HEAD:
DETAIL:  Key already exists in unique index "foo_pkey", modified locally in transaction 789 at ...
        Key (a)=(6); existing local row (6, 6, 6); remote row (6, 7, 8); replica identity (a)=(5).
        Key already exists in unique index "foo_b_key", modified locally in transaction 789 at ...
        Key (b)=(7); existing local row (7, 7, 7); remote row (6, 7, 8); replica identity (a)=(5).
        Key already exists in unique index "foo_c_key", modified locally in transaction 789 at ...
        Key (c)=(8); existing local row (8, 8, 8); remote row (6, 7, 8); replica identity (a)=(5).

V1:
DETAIL:  Could not apply remote row (6, 7, 8) by using replica identity (a)=(5).
        Key (a)=(6) already exists in unique index "foo_pkey", modified locally in transaction 790 at ...: local row (6, 6, 6).
        Key (b)=(7) already exists in unique index "foo_b_key", modified locally in transaction 790 at ...: local row (7, 7, 7).
        Key (c)=(8) already exists in unique index "foo_c_key", modified locally in transaction 790 at ...: local row (8, 8, 8).

V2:
DETAIL:  Could not apply remote change by using replica identity (a)=(5): remote row (6, 7, 8).
        Key (a)=(6) already exists in unique index "foo_pkey", modified locally in transaction 788 at ...: local row (6, 6, 6).
        Key (b)=(7) already exists in unique index "foo_b_key", modified locally in transaction 788 at ...: local row (7, 7, 7).
        Key (c)=(8) already exists in unique index "foo_c_key", modified locally in transaction 788 at ...: local row (8, 8, 8).

Case 2: update_origin_differs
HEAD:
DETAIL:  Updating the row that was modified locally in transaction 790 at ...
        Existing local row (5, 5, 5); remote row (6, 7, 8); replica identity (a)=(5).

V1:
DETAIL:  Remote row (6, 7, 8) was applied but previously modified by different origin.
        Local row (5, 5, 5) detected by replica identity (a)=(5) is being updated, but it was previously modified locally in transaction 790 at ....

V2:
DETAIL:  Updating the row that was modified locally in transaction 790 at ...: local row (5, 5, 5), remote row (6, 7, 8), replica identity (a)=(5).

Case 3: delete_origin_differs with huge column
HEAD:
DETAIL:  Deleting the row that was modified locally in transaction 795 at ...
        Existing local row (1, testtesttesttesttesttesttesttesttesttesttesttesttesttesttesttest...); replica identity (id)=(1).

V1:
DETAIL:  Local row (1, testtesttesttesttesttesttesttesttesttesttesttesttesttesttesttest...) detected by replica identity (id)=(1) is being deleted, but it was previously modified locally in transaction 797 at ....

V2:
DETAIL:  Deleting the row that was modified locally in transaction 807 at ...: local row (1, testtesttesttesttesttesttesttesttesttesttesttesttesttesttesttest...), replica identity (id)=(1).

Case 4: update_deleted
HEAD:
DETAIL:  The row to be updated was deleted locally in transaction 789 at ...
        Remote row (6, 7, 8); replica identity (a)=(5).

V1:
DETAIL:  Could not find remote row (6, 7, 8) by using replica identity (a)=(5).
        Local row was previously deleted locally in transaction 795 at ....

V2:
DETAIL:  Could not find the row by using replica identity (a)=(5): remote row (6, 7, 8).
        The row to be updated was deleted locally in transaction 789 at ....

Best regards,
Hayato Kuroda
FUJITSU LIMITED

---

From: Amit Kapila <[email-masked]>
Date: Fri, 16 Jan 2026 09:16:27 +0530

>         Key (c)=(8) already exists in unique index "foo_c_key", modified locally in transaction 788 at ...: local row (8, 8, 8).

V2 looks better as in V1, if the length of the remote row is large
then it would be inconvenient to read.

> DETAIL:  Deleting the row that was modified locally in transaction 807 at ...: local row (1, testtesttesttesttesttesttesttesttesttesttesttesttesttesttesttest...), replica identity (id)=(1).

In V2 for case-2 and case-3, it seems you only removed 'Existing'
before the local row, is that correct?

>         The row to be updated was deleted locally in transaction 789 at ....

V2 looks better because of the same reason as for case-1.

You haven't shared the details for update/delete_missing and
insert/update_exists. Is it because they haven't changed or something
else?

-- 
With Regards,
Amit Kapila.

---

From: shveta malik <[email-masked]>
Date: Fri, 16 Jan 2026 09:16:55 +0530

>         The row to be updated was deleted locally in transaction 789 at ....

To me v2 of each looks better as all the rows and RI (which could be
huge) are at the end. Let's see what others have to say here.

For delete_origin_differs and update_origin_differs, I considered
using the phrase “by using replica identity,” as we do in other
messages. But it doesn’t read well in this context. For example, the
sentence below sounds slightly awkward:

Updating/Deleting the row that was modified locally in transaction <…>
at <…> using replica identity (a) = (5): local row (5, 5, 5), remote
row (6, 7, 8).

More importantly, this wording may be confusing, as it’s unclear
whether “using replica identity” refers to the current operation or to
the earlier operation that modified the row and caused the origin to
differ. Therefore, I’ll stick with the V2 wording suggested above.

thanks
Shveta

---

From: Amit Kapila <[email-masked]>
Date: Fri, 16 Jan 2026 10:05:30 +0530

> insert/update_exists. Is it because they haven't changed or something

One more point:
HEAD: conflict detected on relation "public.tab_full_pk":
conflict=update_missing
PATCH: conflict update_missing detected on relation "public.tab_full_pk"

I am not very sure whether all users like the change of moving
conflict_type to earlier in the message string even though it appears
better. I think it will be easier for scripts to grep what we have in
HEAD. Can we at least move it to a second patch so that we can discuss
it separately once the DETAIL messages are fixed.

BTW, the patch should update the docs [1] to reflect the changes.

[1] - https://www.postgresql.org/docs/devel/logical-replication-conflicts.html

-- 
With Regards,
Amit Kapila.

---

From: shveta malik <[email-masked]>
Date: Fri, 16 Jan 2026 12:22:50 +0530

Kuroda-San, I looked at the code. Currently it is slightly
complicated. Can we please simplify it by removing different
message-styles for a single conflict. For example
MULTIPLE_UNIQUE_CONFLICTS has below:

1)
 When key and local rows are there:
"Key %s already exists in unique index \"%s\", modified locally in
transaction %u at %s: local row %s.

When key is not there:
"Unique index \"%s\" rejects applying due to local row %s, modified
locally in transaction %u at %s."

When local row is not there:
"Key %s already exists in unique index \"%s\", modified locally in
transaction %u at %s."

When both key and local row is not there:
Remote row violates unique constraint \"%s\", modified locally in
transaction %u at %s."

Can we try to construct message like this: (both key value and local
row at the end)
Key already exists in unique index "conf_tab_pkey", modified locally
in transaction <..> at <...>: Key (a)=(2), local row (2, 2, 2),

We can keep the message same for all 4 cases above.

2)
Also there are many if-else blocks around the below type of message as well:
 DETAIL:  Could not find the row to be updated by using replica
identity (i)=(30): remote row (30, 300).

If we instead have this:
DETAIL: Could not find the row to be updated: replica identity
(i)=(30), remote row (30, 300).

Will it simplify the code, can you please check?

thanks
Shveta

---

From: "Hayato Kuroda (Fujitsu)" <[email-masked]>
Date: Fri, 16 Jan 2026 13:50:18 +0000

Dear hackers,

Thanks for giving comments!

I intended to avoid constructing messages and it added some complexity. I
understood codes were hard to accept, attached is a simplified version. All info
like key, local tuple, remote tuple, and replica identity are shown after the
colon with the order. I also tried to reduce changes as much as possible.
Additionally, changes in ReportApplyConflict() are moved to 0002.

BTW, I found that in CT_UPDATE_ORIGIN_DIFFERS and CT_DELETE_ORIGIN_DIFFERS cases
assume localts is available because it can happen only when the commit_timestamp
is tracked. I think the assumption is OK, but can we add Assert(localts) for them?
If acceptable I can add up more top-up patch.

Rest compares DETAIL part for each conflict reasons with HEAD and patched.

CT_MULTIPLE_UNIQUE_CONFLICTS by INSERT:
HEAD)
Key already exists in unique index "foo_pkey", modified locally in transaction 801 at 2026-xxx.
Key (a)=(6); existing local row (6, 6, 6); remote row (6, 7, 8).
Key already exists in unique index "foo_b_key", modified locally in transaction 801 at 2026-xxx.
Key (b)=(7); existing local row (7, 7, 7); remote row (6, 7, 8).
Key already exists in unique index "foo_c_key", modified locally in transaction 801 at 2026-xxx.
Key (c)=(8); existing local row (8, 8, 8); remote row (6, 7, 8).

Patched)
Could not apply remote change: remote row (6, 7, 8).
Key already exists in unique index "foo_pkey", modified locally in transaction 801 at 2026-xxx: key (a)=(6), local row (6, 6, 6).
Key already exists in unique index "foo_b_key", modified locally in transaction 801 at 2026-xxx: key (b)=(7), local row (7, 7, 7).
Key already exists in unique index "foo_c_key", modified locally in transaction 801 at 2026-xxx: key (c)=(8), local row (8, 8, 8).

CT_MULTIPLE_UNIQUE_CONFLICTS by UPDATE:
HEAD)
Key already exists in unique index "foo_pkey", modified locally in transaction 801 at 2026-xxx.
Key (a)=(6); existing local row (6, 6, 6); remote row (6, 7, 8); replica identity (a)=(5).
Key already exists in unique index "foo_b_key", modified locally in transaction 801 at 2026-xxx.
Key (b)=(7); existing local row (7, 7, 7); remote row (6, 7, 8); replica identity (a)=(5).
Key already exists in unique index "foo_c_key", modified locally in transaction 801 at 2026-xxx.
Key (c)=(8); existing local row (8, 8, 8); remote row (6, 7, 8); replica identity (a)=(5).

Patched)
Could not apply remote change: remote row (6, 7, 8), replica identity (a)=(5).
Key already exists in unique index "foo_pkey", modified locally in transaction 804 at 2026-xxx: key (a)=(6), local row (6, 6, 6).
Key already exists in unique index "foo_b_key", modified locally in transaction 804 at 2026-xxx: key (b)=(7), local row (7, 7, 7).
Key already exists in unique index "foo_c_key", modified locally in transaction 804 at 2026-xxx: key (c)=(8), local row (8, 8, 8).

CT_UPDATE_ORIGIN_DIFFERS:
HEAD)
Updating the row that was modified locally in transaction 802 at 2026-xxx.
Existing local row (5, 5, 5); remote row (6, 7, 8); replica identity (a)=(5).

Patched)
Updating the row that was modified locally in transaction 802 at 2026-xxx: local row (5, 5, 5), remote row (6, 7, 8), replica identity (a)=(5).

CT_UPDATE_MISSING:
HEAD)
Could not find the row to be updated.
Remote row (6, 7, 8); replica identity (a)=(6).

Patched)
Could not find the row to be updated: remote row (6, 7, 8), replica identity (a)=(6).

CT_DELETE_ORIGIN_DIFFERS:
HEAD)
Deleting the row that was modified locally in transaction 818 at 2026-xxx.
Existing local row (5, 5, 5); replica identity (a)=(5).

Patched)
Deleting the row that was modified locally in transaction 802 at 2026-xxx: local row (5, 5, 5), replica identity (a)=(5).

CT_UPDATE_DELETED:
HEAD)
The row to be updated was deleted locally in transaction 801 at 2026-xxx.
Remote row (6, 7, 8); replica identity (a)=(5).

Patched)
Could not find the row to be updated: remote row (6, 7, 8), replica identity (a)=(5).
The row to be updated was deleted locally in transaction 801 at 2026-xxx.

CT_DELETE_MISSING:
HEAD)
Could not find the row to be deleted.
Replica identity (a)=(6).

Patched)
Could not find the row to be deleted: replica identity (a)=(6).

Best regards,
Hayato Kuroda
FUJITSU LIMITED

---

From: shveta malik <[email-masked]>
Date: Mon, 19 Jan 2026 10:11:02 +0530

> If acceptable I can add up more top-up patch.

+1, makes sense to me.

Please find a few trivial comments:

1)
append_tuple_value_detail():
+/*
+ * Helper function to build the additional details for conflicting key,
+ * existing local row, remote row, and replica identity columns.
+ */
we can get rid of 'existing' from this comment too.

Same is true for header-comment of obtain_tuple_values().

2)

+ bool comma_needed = false;

Shall we change above to  'bool first = true'.  'comma_needed' looks
slightly odd.

3)
errdetail_apply_conflict()

- *
- * 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
- *

Why did we remove this part? IMO, it still makes sense.

thanks
Shveta

---

From: "Hayato Kuroda (Fujitsu)" <[email-masked]>
Date: Mon, 19 Jan 2026 05:26:04 +0000

Dear Shveta,

> Same is true for header-comment of obtain_tuple_values().

I removed all the term 'exsting local row', not only the function header.

> Why did we remove this part? IMO, it still makes sense.

I removed because INSERT_EXISTS/UPDATE_EXISTS/CT_UPDATE_DELETED has two lines,
and each ones have the explanation and display part.

But... after reading again, I noticed that "The DETAIL line comprises..." meant
that even if DETAIL has two lines, each one consists of parts. Thus I restored.

Here is an updated version. Comments from Shveta were addressed and a translator
note was added in append_tuple_value_detail(). Outputs were not updated.

Lastly, I was not included example for INSERT_EXISTS and UPDATE_EXISTS, because
I thought these cases were included in CT_MULTIPLE_UNIQUE_CONFLICTS. But let me
share them just in case. Here a long string was used as the value.

INSERT_EXISTS:
Head)
DETAIL:  Key already exists in unique index "huga_pkey", modified locally in transaction 800 at 2026-xxx.
Key (id)=(1); existing local row (1, testtesttesttesttesttesttesttesttesttesttesttesttesttesttesttest...); remote row (1, testtesttesttesttesttesttesttesttesttesttesttesttesttesttesttest...).

Patched)
DETAIL:  Could not apply remote change: remote row (1, testtesttesttesttesttesttesttesttesttesttesttesttesttesttesttest...).
Key already exists in unique index "huga_pkey", modified locally in transaction 800 at 2026-xxx: key (id)=(1), local row (1, testtesttesttesttesttesttesttesttesttesttesttesttesttesttesttest...).

UPDATE_EXISTS:
Head)
DETAIL:  Key already exists in unique index "huga_pkey", modified locally in transaction 801 at 2026-xxx.
Key (id)=(2); existing local row (2, testtesttesttesttesttesttesttesttesttesttesttesttesttesttesttest...); remote row (2, testtesttesttesttesttesttesttesttesttesttesttesttesttesttesttest...); replica identity (id)=(1).

Patched)
DETAIL:  Could not apply remote change: remote row (2, testtesttesttesttesttesttesttesttesttesttesttesttesttesttesttest...), replica identity (id)=(1).
Key already exists in unique index "huga_pkey", modified locally in transaction 801 at 2026-xxx: key (id)=(2), local row (2, testtesttesttesttesttesttesttesttesttesttesttesttesttesttesttest...).

Best regards,
Hayato Kuroda
FUJITSU LIMITED

---

From: Amit Kapila <[email-masked]>
Date: Mon, 19 Jan 2026 12:34:14 +0530

On Mon, Jan 19, 2026 at 10:56 AM Hayato Kuroda (Fujitsu)
<[email-masked]> wrote:

Few comments:
=============
1.
+ append_tuple_value_detail(&err_detail, NULL, NULL,
+   remote_desc, search_desc);
+
+ appendStringInfoString(&err_detail, _(".\n"));

Adding full-stop along with a newline like this appears odd. I think
we did it like this so that the internal function
append_tuple_value_detail() doesn't decide whether the line ends. Is
there any better way or are we okay with this kind of coding? I see
that previously build_tuple_value_details() also appends dot when
required. See below code:
build_tuple_value_details()
{
...
if (tuple_value.len == 0)
return NULL;

appendStringInfoChar(&tuple_value, '.');
…
}

2.
+ if (key_desc)
+ pfree(key_desc);
+ if (search_desc)
+ pfree(search_desc);
+ if (local_desc)
+ pfree(local_desc);
+ if (remote_desc)
+ pfree(remote_desc);

Do we need these retail pfrees and in one in obtain_tuple_values()?
Which context is being used to allocate this memory and won't it reset
after applying or logging this change?

3.
+static void
+obtain_tuple_values(EState

Won't it be better to name this function as get_tuple_values()?

-- 
With Regards,
Amit Kapila.

---

From: "Hayato Kuroda (Fujitsu)" <[email-masked]>
Date: Mon, 19 Jan 2026 10:55:00 +0000

Dear Amit,

> Few comments:

After considering bit more, I found "." can be appended all the cases and
\newline can be added for some cases. Added a parameter to control \newline,
and removed some termination handlings from the errdetail... function.

> after applying or logging this change?

I checked and the context is ApplyMessageContext and it is cleared after each
replication protocol message. All pfree() are not needed anymore.

Fixed the rest and update the commit message. Please see attached.

Best regards,
Hayato Kuroda
FUJITSU LIMITED

---

From: shveta malik <[email-masked]>
Date: Tue, 20 Jan 2026 10:13:28 +0530

> Fixed the rest and update the commit message. Please see attached.

--v6-001 has a trailing whitespace issue.

--FWIW, I like the v6001 style better than v6002, where the conflict
type appears at the end.

> If acceptable I can add up more top-up patch.

I think it is a good idea to add that Assert in a separate patch.

Other than these, I do not have any comments on attached patches..

thanks
Shveta

---

From: "Hayato Kuroda (Fujitsu)" <[email-masked]>
Date: Wed, 21 Jan 2026 02:22:02 +0000

Dear hackers,

Thanks for the comment. I fixed a whitespace issue. I attach only 0001 to focus on it. 
Also, Hou Zhijie gave a comment to improve the code comment (maybe it's under
moderation). His point is that get_tuple_ function can also append the description
for each attribute and append_tuple_value_detail() can handle lists of them.
I've updated based on this, taking into account the lack of permissions.

How do you think?

Best regards,
Hayato Kuroda
FUJITSU LIMITED

---

From: shveta malik <[email-masked]>
Date: Wed, 21 Jan 2026 09:20:07 +0530

> I've updated based on this, taking into account the lack of permissions.

The function re-structuring looks good. No issues observed in my local
testing. Thus the patch LGTM.

thanks
Shveta