From 7763a9d7477915b70e7cda84996f36c3d79501aa Mon Sep 17 00:00:00 2001
From: Dilip Kumar <dilipkumar@Dilip.local>
Date: Sun, 5 Apr 2026 17:02:01 +0530
Subject: [PATCH v58 6/7] Documentation patch

---
 doc/src/sgml/ddl.sgml                     |  20 +
 doc/src/sgml/glossary.sgml                |  12 +
 doc/src/sgml/logical-replication.sgml     | 669 +++++++++++++---------
 doc/src/sgml/ref/alter_subscription.sgml  |  13 +-
 doc/src/sgml/ref/create_subscription.sgml |  51 ++
 5 files changed, 494 insertions(+), 271 deletions(-)

diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml
index 45db20d47d8..a784884df85 100644
--- a/doc/src/sgml/ddl.sgml
+++ b/doc/src/sgml/ddl.sgml
@@ -3759,6 +3759,26 @@ REVOKE CREATE ON SCHEMA public FROM PUBLIC;
    </para>
   </sect2>
 
+  <sect2 id="ddl-schemas-conflict">
+   <title>The Conflict Schema</title>
+
+   <indexterm zone="ddl-schemas-conflict">
+    <primary>conflict</primary>
+    <secondary>schema</secondary>
+   </indexterm>
+
+   <para>
+    Similarly, the <literal>pg_conflict</literal> schema (sometimes referred to
+    as the <emphasis>conflict schema</emphasis>) contains system managed
+    conflict log tables used for logical replication conflict tracking. These
+    tables are created and maintained by the system and are not intended for
+    direct user manipulation. Unlike <literal>pg_catalog</literal>, the
+    <literal>pg_catalog</literal> schema is not implicitly included in the
+    search path, so objects within it must be referenced explicitly or by
+    adjusting the search path.
+   </para>
+  </sect2>
+
   <sect2 id="ddl-schemas-patterns">
    <title>Usage Patterns</title>
 
diff --git a/doc/src/sgml/glossary.sgml b/doc/src/sgml/glossary.sgml
index b881ae71198..57b6970ad9f 100644
--- a/doc/src/sgml/glossary.sgml
+++ b/doc/src/sgml/glossary.sgml
@@ -506,6 +506,18 @@
    </glossdef>
   </glossentry>
 
+  <glossentry id="glossary-conflict-schema">
+   <glossterm>conflict schema</glossterm>
+   <glossdef>
+    <para>
+     The <literal>pg_conflict</literal> schema that contains system-managed
+     conflict log tables for logical replication. These tables are created
+     and maintained automatically by the system and are not intended for
+     direct user manipulation. See <xref linkend="ddl-schemas-conflict"/>.
+    </para>
+   </glossdef>
+  </glossentry>
+
   <glossentry id="glossary-connection">
    <glossterm>Connection</glossterm>
    <glossdef>
diff --git a/doc/src/sgml/logical-replication.sgml b/doc/src/sgml/logical-replication.sgml
index 5befefd9c5a..e1d6c9423b1 100644
--- a/doc/src/sgml/logical-replication.sgml
+++ b/doc/src/sgml/logical-replication.sgml
@@ -293,6 +293,21 @@
    option of <command>CREATE SUBSCRIPTION</command> for details.
   </para>
 
+  <para>
+   <link linkend="logical-replication-conflicts">Conflicts</link> that occur
+   during replication are, by default, logged as plain text in the server log,
+   which can make automated monitoring and analysis difficult. The
+   <command>CREATE SUBSCRIPTION</command> command provides the
+   <link linkend="sql-createsubscription-params-with-conflict-log-destination">
+   <literal>conflict_log_destination</literal></link> option to record detailed
+   conflict information in a structured, queryable format. When this parameter
+   is set to <literal>table</literal> or <literal>all</literal>, the system
+   automatically manages a dedicated <firstterm>conflict log table</firstterm>,
+   which is created an dropped along with the subscription. This significantly
+   improves post-mortem analysis and operational visibility of the replication
+   setup.
+  </para>
+
   <sect2 id="logical-replication-subscription-slot">
    <title>Logical Replication Slot Management</title>
 
@@ -2009,120 +2024,225 @@ Included in publications:
    operations will simply be skipped.
   </para>
 
-  <para>
-   Additional logging is triggered, and the conflict statistics are collected (displayed in the
-   <link linkend="monitoring-pg-stat-subscription-stats"><structname>pg_stat_subscription_stats</structname></link> view)
-   in the following <firstterm>conflict</firstterm> cases:
-   <variablelist>
-    <varlistentry id="conflict-insert-exists" xreflabel="insert_exists">
-     <term><literal>insert_exists</literal></term>
-     <listitem>
-      <para>
-       Inserting a row that violates a <literal>NOT DEFERRABLE</literal>
-       unique constraint. Note that to log the origin and commit
-       timestamp details of the conflicting key,
-       <link linkend="guc-track-commit-timestamp"><varname>track_commit_timestamp</varname></link>
-       should be enabled on the subscriber. In this case, an error will be
-       raised until the conflict is resolved manually.
-      </para>
-     </listitem>
-    </varlistentry>
-    <varlistentry id="conflict-update-origin-differs" xreflabel="update_origin_differs">
-     <term><literal>update_origin_differs</literal></term>
-     <listitem>
-      <para>
-       Updating a row that was previously modified by another origin.
-       Note that this conflict can only be detected when
-       <link linkend="guc-track-commit-timestamp"><varname>track_commit_timestamp</varname></link>
-       is enabled on the subscriber. Currently, the update is always applied
-       regardless of the origin of the local row.
-      </para>
-     </listitem>
-    </varlistentry>
-    <varlistentry id="conflict-update-exists" xreflabel="update_exists">
-     <term><literal>update_exists</literal></term>
-     <listitem>
-      <para>
-       The updated value of a row violates a <literal>NOT DEFERRABLE</literal>
-       unique constraint. Note that to log the origin and commit
-       timestamp details of the conflicting key,
-       <link linkend="guc-track-commit-timestamp"><varname>track_commit_timestamp</varname></link>
-       should be enabled on the subscriber. In this case, an error will be
-       raised until the conflict is resolved manually. Note that when updating a
-       partitioned table, if the updated row value satisfies another partition
-       constraint resulting in the row being inserted into a new partition, the
-       <literal>insert_exists</literal> conflict may arise if the new row
-       violates a <literal>NOT DEFERRABLE</literal> unique constraint.
-      </para>
-     </listitem>
-    </varlistentry>
-    <varlistentry id="conflict-update-deleted" xreflabel="update_deleted">
-     <term><literal>update_deleted</literal></term>
-     <listitem>
-      <para>
-       The tuple to be updated was concurrently deleted by another origin. The
-       update will simply be skipped in this scenario. Note that this conflict
-       can only be detected when
-       <link linkend="guc-track-commit-timestamp"><varname>track_commit_timestamp</varname></link>
-       and <link linkend="sql-createsubscription-params-with-retain-dead-tuples"><literal>retain_dead_tuples</literal></link>
-       are enabled. Note that if a tuple cannot be found due to the table being
-       truncated, only a <literal>update_missing</literal> conflict will
-       arise. Additionally, if the tuple was deleted by the same origin, an
-       <literal>update_missing</literal> conflict will arise.
-      </para>
-     </listitem>
-    </varlistentry>
-    <varlistentry id="conflict-update-missing" xreflabel="update_missing">
-     <term><literal>update_missing</literal></term>
-     <listitem>
-      <para>
-       The row to be updated was not found. The update will simply be
-       skipped in this scenario.
-      </para>
-     </listitem>
-    </varlistentry>
-    <varlistentry id="conflict-delete-origin-differs" xreflabel="delete_origin_differs">
-     <term><literal>delete_origin_differs</literal></term>
-     <listitem>
-      <para>
-       Deleting a row that was previously modified by another origin. Note that
-       this conflict can only be detected when
-       <link linkend="guc-track-commit-timestamp"><varname>track_commit_timestamp</varname></link>
-       is enabled on the subscriber. Currently, the delete is always applied
-       regardless of the origin of the local row.
-      </para>
-     </listitem>
-    </varlistentry>
-    <varlistentry id="conflict-delete-missing" xreflabel="delete_missing">
-     <term><literal>delete_missing</literal></term>
-     <listitem>
-      <para>
-       The row to be deleted was not found. The delete will simply be
-       skipped in this scenario.
-      </para>
-     </listitem>
-    </varlistentry>
-    <varlistentry id="conflict-multiple-unique-conflicts" xreflabel="multiple_unique_conflicts">
-     <term><literal>multiple_unique_conflicts</literal></term>
-     <listitem>
-      <para>
-       Inserting or updating a row violates multiple
-       <literal>NOT DEFERRABLE</literal> unique constraints. Note that to log
-       the origin and commit timestamp details of conflicting keys, ensure
-       that <link linkend="guc-track-commit-timestamp"><varname>track_commit_timestamp</varname></link>
-       is enabled on the subscriber. In this case, an error will be raised until
-       the conflict is resolved manually.
-      </para>
-     </listitem>
-    </varlistentry>
-   </variablelist>
-    Note that there are other conflict scenarios, such as exclusion constraint
-    violations. Currently, we do not provide additional details for them in the
-    log.
-  </para>
+  <sect2 id="logical-replication-conflict-logging">
+   <title>Conflict logging</title>
+   <para>
+    Additional logging is triggered, and the conflict statistics are collected (displayed in the
+    <link linkend="monitoring-pg-stat-subscription-stats"><structname>pg_stat_subscription_stats</structname></link> view)
+    in the following <firstterm>conflict</firstterm> cases:
+    <variablelist>
+     <varlistentry id="conflict-insert-exists" xreflabel="insert_exists">
+      <term><literal>insert_exists</literal></term>
+      <listitem>
+       <para>
+        Inserting a row that violates a <literal>NOT DEFERRABLE</literal>
+        unique constraint. Note that to log the origin and commit
+        timestamp details of the conflicting key,
+        <link linkend="guc-track-commit-timestamp"><varname>track_commit_timestamp</varname></link>
+        should be enabled on the subscriber. In this case, an error will be
+        raised until the conflict is resolved manually.
+       </para>
+      </listitem>
+     </varlistentry>
+     <varlistentry id="conflict-update-origin-differs" xreflabel="update_origin_differs">
+      <term><literal>update_origin_differs</literal></term>
+      <listitem>
+       <para>
+        Updating a row that was previously modified by another origin.
+        Note that this conflict can only be detected when
+        <link linkend="guc-track-commit-timestamp"><varname>track_commit_timestamp</varname></link>
+        is enabled on the subscriber. Currently, the update is always applied
+        regardless of the origin of the local row.
+       </para>
+      </listitem>
+     </varlistentry>
+     <varlistentry id="conflict-update-exists" xreflabel="update_exists">
+      <term><literal>update_exists</literal></term>
+      <listitem>
+       <para>
+        The updated value of a row violates a <literal>NOT DEFERRABLE</literal>
+        unique constraint. Note that to log the origin and commit
+        timestamp details of the conflicting key,
+        <link linkend="guc-track-commit-timestamp"><varname>track_commit_timestamp</varname></link>
+        should be enabled on the subscriber. In this case, an error will be
+        raised until the conflict is resolved manually. Note that when updating a
+        partitioned table, if the updated row value satisfies another partition
+        constraint resulting in the row being inserted into a new partition, the
+        <literal>insert_exists</literal> conflict may arise if the new row
+        violates a <literal>NOT DEFERRABLE</literal> unique constraint.
+       </para>
+      </listitem>
+     </varlistentry>
+     <varlistentry id="conflict-update-deleted" xreflabel="update_deleted">
+      <term><literal>update_deleted</literal></term>
+      <listitem>
+       <para>
+        The tuple to be updated was concurrently deleted by another origin. The
+        update will simply be skipped in this scenario. Note that this conflict
+        can only be detected when
+        <link linkend="guc-track-commit-timestamp"><varname>track_commit_timestamp</varname></link>
+        and <link linkend="sql-createsubscription-params-with-retain-dead-tuples"><literal>retain_dead_tuples</literal></link>
+        are enabled. Note that if a tuple cannot be found due to the table being
+        truncated, only a <literal>update_missing</literal> conflict will
+        arise. Additionally, if the tuple was deleted by the same origin, an
+        <literal>update_missing</literal> conflict will arise.
+       </para>
+      </listitem>
+     </varlistentry>
+     <varlistentry id="conflict-update-missing" xreflabel="update_missing">
+      <term><literal>update_missing</literal></term>
+      <listitem>
+       <para>
+        The row to be updated was not found. The update will simply be
+        skipped in this scenario.
+       </para>
+      </listitem>
+     </varlistentry>
+     <varlistentry id="conflict-delete-origin-differs" xreflabel="delete_origin_differs">
+      <term><literal>delete_origin_differs</literal></term>
+      <listitem>
+       <para>
+        Deleting a row that was previously modified by another origin. Note that
+        this conflict can only be detected when
+        <link linkend="guc-track-commit-timestamp"><varname>track_commit_timestamp</varname></link>
+        is enabled on the subscriber. Currently, the delete is always applied
+        regardless of the origin of the local row.
+       </para>
+      </listitem>
+     </varlistentry>
+     <varlistentry id="conflict-delete-missing" xreflabel="delete_missing">
+      <term><literal>delete_missing</literal></term>
+      <listitem>
+       <para>
+        The row to be deleted was not found. The delete will simply be
+        skipped in this scenario.
+       </para>
+      </listitem>
+     </varlistentry>
+     <varlistentry id="conflict-multiple-unique-conflicts" xreflabel="multiple_unique_conflicts">
+      <term><literal>multiple_unique_conflicts</literal></term>
+      <listitem>
+       <para>
+        Inserting or updating a row violates multiple
+        <literal>NOT DEFERRABLE</literal> unique constraints. Note that to log
+        the origin and commit timestamp details of conflicting keys, ensure
+        that <link linkend="guc-track-commit-timestamp"><varname>track_commit_timestamp</varname></link>
+        is enabled on the subscriber. In this case, an error will be raised until
+        the conflict is resolved manually.
+       </para>
+      </listitem>
+     </varlistentry>
+    </variablelist>
+     Note that there are other conflict scenarios, such as exclusion constraint
+     violations. Currently, we do not provide additional details for them in the
+     log.
+   </para>
+  </sect2>
 
-  <para>
-   The log format for logical replication conflicts is as follows:
+  <sect2 id="logical-replication-conflict-table-based-logging">
+   <title>Table-based logging</title>
+   <para>
+    If <link linkend="sql-createsubscription-params-with-conflict-log-destination"><literal>conflict_log_destination</literal></link>
+    parameter is set to <literal>table</literal> or <literal>all</literal> then
+    a dedicated conflict log table will be automatically created. This table is
+    created in the <literal>pg_conflict</literal> namespace. The name of the
+    conflict log table is
+    <literal>pg_conflict_log_for_subid_&lt;subid&gt;</literal>. The predefined
+    schema of this table is detailed in
+    <xref linkend="logical-replication-conflict-log-schema"/>.
+   </para>
+
+   <table id="logical-replication-conflict-log-schema">
+    <title>Conflict Log Table Schema</title>
+    <tgroup cols="3">
+     <thead>
+      <row>
+       <entry>Column</entry>
+       <entry>Type</entry>
+       <entry>Description</entry>
+      </row>
+     </thead>
+     <tbody>
+      <row>
+       <entry><literal>relid</literal></entry>
+       <entry><type>oid</type></entry>
+       <entry>The OID of the local table where the conflict occurred.</entry>
+      </row>
+      <row>
+       <entry><literal>schemaname</literal></entry>
+       <entry><type>text</type></entry>
+       <entry>The schema name of the conflicting table.</entry>
+      </row>
+      <row>
+       <entry><literal>relname</literal></entry>
+       <entry><type>text</type></entry>
+       <entry>The name of the conflicting table.</entry>
+      </row>
+      <row>
+       <entry><literal>conflict_type</literal></entry>
+       <entry><type>text</type></entry>
+       <entry>The type of conflict that occurred (e.g., <literal>insert_exists</literal>).</entry>
+      </row>
+      <row>
+       <entry><literal>remote_xid</literal></entry>
+       <entry><type>xid</type></entry>
+       <entry>The remote transaction ID that caused the conflict.</entry>
+      </row>
+      <row>
+       <entry><literal>remote_commit_lsn</literal></entry>
+       <entry><type>pg_lsn</type></entry>
+       <entry>The final LSN of the remote transaction.</entry>
+      </row>
+      <row>
+       <entry><literal>remote_commit_ts</literal></entry>
+       <entry><type>timestamptz</type></entry>
+       <entry>The remote commit timestamp of the remote transaction.</entry>
+      </row>
+      <row>
+       <entry><literal>remote_origin</literal></entry>
+       <entry><type>text</type></entry>
+       <entry>The origin of the remote transaction.</entry>
+      </row>
+      <row>
+       <entry><literal>remote_tuple</literal></entry>
+       <entry><type>json</type></entry>
+       <entry>The JSON representation of the incoming remote row that caused
+       the conflict.</entry>
+      </row>
+      <row>
+       <entry><literal>replica_identity</literal></entry>
+       <entry><type>json</type></entry>
+       <entry>The JSON representation of the replica identity.</entry>
+      </row>
+      <row>
+       <entry><literal>local_conflicts</literal></entry>
+       <entry><type>json[]</type></entry>
+       <entry>
+        An array of JSON objects representing the state of existing local
+        row(s) that caused the conflict. Each object includes the local
+        transaction ID (<literal>xid</literal>), commit timestamp
+        (<literal>commit_ts</literal>), origin (<literal>origin</literal>),
+        conflicting key data (<literal>key</literal>), and the full local row
+        image (<literal>tuple</literal>).
+       </entry>
+      </row>
+     </tbody>
+    </tgroup>
+   </table>
+
+   <para>
+    The conflicting row data, including the incoming remote row (<literal>remote_tuple</literal>)
+    and the associated local conflict details (<literal>local_conflicts</literal>), is stored in
+    <type>JSON</type> formats for flexible querying and analysis.
+   </para>
+  </sect2>
+
+  <sect2 id="logical-replication-conflict-file-based-logging">
+   <title>File-based logging</title>
+   <para>
+    If <link linkend="sql-createsubscription-params-with-conflict-log-destination"><literal>conflict_log_destination</literal></link>
+    is set to <literal>log</literal> or <literal>all</literal> then conflicts
+    are logged to the server using the following format:
 <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> [, ... ]].
@@ -2135,182 +2255,185 @@ DETAIL:  <replaceable class="parameter">detailed_explanation</replaceable>[: <re
     <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>
 
-   The log provides the following information:
-   <variablelist>
-    <varlistentry>
-     <term><literal>LOG</literal></term>
-      <listitem>
+    The log provides the following information:
+    <variablelist>
+     <varlistentry>
+      <term><literal>LOG</literal></term>
+       <listitem>
+        <itemizedlist>
+         <listitem>
+          <para>
+          <replaceable>schemaname</replaceable>.<replaceable>tablename</replaceable>
+          identifies the local relation involved in the conflict.
+          </para>
+         </listitem>
+         <listitem>
+          <para>
+          <replaceable>conflict_type</replaceable> is the type of conflict that occurred
+          (e.g., <literal>insert_exists</literal>, <literal>update_exists</literal>).
+          </para>
+         </listitem>
+        </itemizedlist>
+       </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><literal>DETAIL</literal></term>
+       <listitem>
        <itemizedlist>
         <listitem>
          <para>
-         <replaceable>schemaname</replaceable>.<replaceable>tablename</replaceable>
-         identifies the local relation involved in the conflict.
+          <replaceable class="parameter">detailed_explanation</replaceable> includes
+          the origin, transaction ID, and commit timestamp of the transaction that
+          modified the local row, if available.
          </para>
         </listitem>
         <listitem>
          <para>
-         <replaceable>conflict_type</replaceable> is the type of conflict that occurred
-         (e.g., <literal>insert_exists</literal>, <literal>update_exists</literal>).
+          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.
+         </para>
+        </listitem>
+        <listitem>
+         <para>
+          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
+          <literal>multiple_unique_conflicts</literal> conflicts.
+         </para>
+        </listitem>
+        <listitem>
+         <para>
+          The <literal>remote row</literal> 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 row will be null
+          if the value is unchanged and toasted.
+         </para>
+        </listitem>
+        <listitem>
+         <para>
+          The <literal>replica identity</literal> section includes the replica
+          identity key values that were used to search for the existing local
+          row to be updated or deleted. This may include the full row value
+          if the local relation is marked with
+          <link linkend="sql-altertable-replica-identity-full"><literal>REPLICA IDENTITY FULL</literal></link>.
+         </para>
+        </listitem>
+        <listitem>
+         <para>
+          <replaceable class="parameter">column_name</replaceable> is the column name.
+          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.
+         </para>
+        </listitem>
+        <listitem>
+         <para>
+          <replaceable class="parameter">column_value</replaceable> is the column value.
+          The large column values are truncated to 64 bytes.
+         </para>
+        </listitem>
+        <listitem>
+         <para>
+          Note that in case of <literal>multiple_unique_conflicts</literal> conflict,
+          multiple <replaceable class="parameter">detailed_explanation</replaceable>
+          and <replaceable class="parameter">detail_values</replaceable> lines
+          will be generated, each detailing the conflict information associated
+          with distinct unique constraints.
          </para>
         </listitem>
        </itemizedlist>
       </listitem>
-    </varlistentry>
-
-    <varlistentry>
-     <term><literal>DETAIL</literal></term>
-      <listitem>
-      <itemizedlist>
-       <listitem>
-        <para>
-         <replaceable class="parameter">detailed_explanation</replaceable> includes
-         the origin, transaction ID, and commit timestamp of the transaction that
-         modified the local row, if available.
-        </para>
-       </listitem>
-       <listitem>
-        <para>
-         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.
-        </para>
-       </listitem>
-       <listitem>
-        <para>
-         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
-         <literal>multiple_unique_conflicts</literal> conflicts.
-        </para>
-       </listitem>
-       <listitem>
-        <para>
-         The <literal>remote row</literal> 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 row will be null
-         if the value is unchanged and toasted.
-        </para>
-       </listitem>
-       <listitem>
-        <para>
-         The <literal>replica identity</literal> section includes the replica
-         identity key values that were used to search for the existing local
-         row to be updated or deleted. This may include the full row value
-         if the local relation is marked with
-         <link linkend="sql-altertable-replica-identity-full"><literal>REPLICA IDENTITY FULL</literal></link>.
-        </para>
-       </listitem>
-       <listitem>
-        <para>
-         <replaceable class="parameter">column_name</replaceable> is the column name.
-         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.
-        </para>
-       </listitem>
-       <listitem>
-        <para>
-         <replaceable class="parameter">column_value</replaceable> is the column value.
-         The large column values are truncated to 64 bytes.
-        </para>
-       </listitem>
-       <listitem>
-        <para>
-         Note that in case of <literal>multiple_unique_conflicts</literal> conflict,
-         multiple <replaceable class="parameter">detailed_explanation</replaceable>
-         and <replaceable class="parameter">detail_values</replaceable> lines
-         will be generated, each detailing the conflict information associated
-         with distinct unique
-         constraints.
-        </para>
-       </listitem>
-      </itemizedlist>
-     </listitem>
-    </varlistentry>
-   </variablelist>
-  </para>
+     </varlistentry>
+    </variablelist>
+   </para>
+  </sect2>
 
-  <para>
-   Logical replication operations are performed with the privileges of the role
-   which owns the subscription.  Permissions failures on target tables will
-   cause replication conflicts, as will enabled
-   <link linkend="ddl-rowsecurity">row-level security</link> on target tables
-   that the subscription owner is subject to, without regard to whether any
-   policy would ordinarily reject the <command>INSERT</command>,
-   <command>UPDATE</command>, <command>DELETE</command> or
-   <command>TRUNCATE</command> which is being replicated.  This restriction on
-   row-level security may be lifted in a future version of
-   <productname>PostgreSQL</productname>.
-  </para>
+  <sect2 id="logical-replication-conflict-notes">
+   <title>Notes</title>
+   <para>
+    Logical replication operations are performed with the privileges of the role
+    which owns the subscription.  Permissions failures on target tables will
+    cause replication conflicts, as will enabled
+    <link linkend="ddl-rowsecurity">row-level security</link> on target tables
+    that the subscription owner is subject to, without regard to whether any
+    policy would ordinarily reject the <command>INSERT</command>,
+    <command>UPDATE</command>, <command>DELETE</command> or
+    <command>TRUNCATE</command> which is being replicated.  This restriction on
+    row-level security may be lifted in a future version of
+    <productname>PostgreSQL</productname>.
+   </para>
 
-  <para>
-   A conflict that produces an error will stop the replication; it must be
-   resolved manually by the user.  Details about the conflict can be found in
-   the subscriber's server log.
-  </para>
+   <para>
+    A conflict that produces an error will stop the replication; it must be
+    resolved manually by the user.  Details about the conflict can be found in
+    the subscriber's server log.
+   </para>
 
-  <para>
-   The resolution can be done either by changing data or permissions on the subscriber so
-   that it does not conflict with the incoming change or by skipping the
-   transaction that conflicts with the existing data.  When a conflict produces
-   an error, the replication won't proceed, and the logical replication worker will
-   emit the following kind of message to the subscriber's server log:
+   <para>
+    The resolution can be done either by changing data or permissions on the subscriber so
+    that it does not conflict with the incoming change or by skipping the
+    transaction that conflicts with the existing data.  When a conflict produces
+    an error, the replication won't proceed, and the logical replication worker will
+    emit the following kind of message to the subscriber's server log:
 <screen>
 ERROR:  conflict detected on relation "public.test": conflict=insert_exists
 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
-   the replication origin name can be found from the server log (LSN 0/014C0378 and
-   replication origin <literal>pg_16395</literal> in the above case).  The
-   transaction that produced the conflict can be skipped by using
-   <link linkend="sql-altersubscription-params-skip"><command>ALTER SUBSCRIPTION ... SKIP</command></link>
-   with the finish LSN
-   (i.e., LSN 0/014C0378).  The finish LSN could be an LSN at which the transaction
-   is committed or prepared on the publisher.  Alternatively, the transaction can
-   also be skipped by calling the <link linkend="pg-replication-origin-advance">
-   <function>pg_replication_origin_advance()</function></link> function.
-   Before using this function, the subscription needs to be disabled temporarily
-   either by <link linkend="sql-altersubscription-params-disable">
-   <command>ALTER SUBSCRIPTION ... DISABLE</command></link> or, the
-   subscription can be used with the
-   <link linkend="sql-createsubscription-params-with-disable-on-error"><literal>disable_on_error</literal></link>
-   option. Then, you can use <function>pg_replication_origin_advance()</function>
-   function with the <parameter>node_name</parameter> (i.e., <literal>pg_16395</literal>)
-   and the next LSN of the finish LSN (i.e., 0/014C0379).  The current position of
-   origins can be seen in the <link linkend="view-pg-replication-origin-status">
-   <structname>pg_replication_origin_status</structname></link> system view.
-   Please note that skipping the whole transaction includes skipping changes that
-   might not violate any constraint.  This can easily make the subscriber
-   inconsistent.
-   The additional details regarding conflicting rows, such as their origin and
-   commit timestamp can be seen in the <literal>DETAIL</literal> line of the
-   log. But note that this information is only available when
-   <link linkend="guc-track-commit-timestamp"><varname>track_commit_timestamp</varname></link>
-   is enabled on the subscriber. Users can use this information to decide
-   whether to retain the local change or adopt the remote alteration. For
-   instance, the <literal>DETAIL</literal> line in the above log indicates that
-   the existing row was modified locally. Users can manually perform a
-   remote-change-win.
-  </para>
-
-  <para>
-   When the
-   <link linkend="sql-createsubscription-params-with-streaming"><literal>streaming</literal></link>
-   mode is <literal>parallel</literal>, the finish LSN of failed transactions
-   may not be logged. In that case, it may be necessary to change the streaming
-   mode to <literal>on</literal> or <literal>off</literal> and cause the same
-   conflicts again so the finish LSN of the failed transaction will be written
-   to the server log. For the usage of finish LSN, please refer to <link
-   linkend="sql-altersubscription"><command>ALTER SUBSCRIPTION ...
-   SKIP</command></link>.
-  </para>
+    The LSN of the transaction that contains the change violating the constraint and
+    the replication origin name can be found from the server log (LSN 0/014C0378 and
+    replication origin <literal>pg_16395</literal> in the above case).  The
+    transaction that produced the conflict can be skipped by using
+    <link linkend="sql-altersubscription-params-skip"><command>ALTER SUBSCRIPTION ... SKIP</command></link>
+    with the finish LSN
+    (i.e., LSN 0/014C0378).  The finish LSN could be an LSN at which the transaction
+    is committed or prepared on the publisher.  Alternatively, the transaction can
+    also be skipped by calling the <link linkend="pg-replication-origin-advance">
+    <function>pg_replication_origin_advance()</function></link> function.
+    Before using this function, the subscription needs to be disabled temporarily
+    either by <link linkend="sql-altersubscription-params-disable">
+    <command>ALTER SUBSCRIPTION ... DISABLE</command></link> or, the
+    subscription can be used with the
+    <link linkend="sql-createsubscription-params-with-disable-on-error"><literal>disable_on_error</literal></link>
+    option. Then, you can use <function>pg_replication_origin_advance()</function>
+    function with the <parameter>node_name</parameter> (i.e., <literal>pg_16395</literal>)
+    and the next LSN of the finish LSN (i.e., 0/014C0379).  The current position of
+    origins can be seen in the <link linkend="view-pg-replication-origin-status">
+    <structname>pg_replication_origin_status</structname></link> system view.
+    Please note that skipping the whole transaction includes skipping changes that
+    might not violate any constraint.  This can easily make the subscriber
+    inconsistent.
+    The additional details regarding conflicting rows, such as their origin and
+    commit timestamp can be seen in the <literal>DETAIL</literal> line of the
+    log. But note that this information is only available when
+    <link linkend="guc-track-commit-timestamp"><varname>track_commit_timestamp</varname></link>
+    is enabled on the subscriber. Users can use this information to decide
+    whether to retain the local change or adopt the remote alteration. For
+    instance, the <literal>DETAIL</literal> line in the above log indicates that
+    the existing row was modified locally. Users can manually perform a
+    remote-change-win.
+   </para>
+
+   <para>
+    When the
+    <link linkend="sql-createsubscription-params-with-streaming"><literal>streaming</literal></link>
+    mode is <literal>parallel</literal>, the finish LSN of failed transactions
+    may not be logged. In that case, it may be necessary to change the streaming
+    mode to <literal>on</literal> or <literal>off</literal> and cause the same
+    conflicts again so the finish LSN of the failed transaction will be written
+    to the server log. For the usage of finish LSN, please refer to <link
+    linkend="sql-altersubscription"><command>ALTER SUBSCRIPTION ...
+    SKIP</command></link>.
+   </para>
+  </sect2>
  </sect1>
 
  <sect1 id="logical-replication-restrictions">
@@ -2415,6 +2538,14 @@ CONTEXT:  processing remote data for replication origin "pg_16395" during "INSER
      key or replica identity defined for it.
     </para>
    </listitem>
+
+   <listitem>
+    <para>
+     Conflict log tables (see <link linkend="sql-createsubscription-params-with-conflict-log-destination"><literal>conflict_log_destination</literal></link> parameter)
+     are never published, even when using <literal>FOR ALL TABLES</literal> in a
+     publication.
+    </para>
+   </listitem>
   </itemizedlist>
  </sect1>
 
diff --git a/doc/src/sgml/ref/alter_subscription.sgml b/doc/src/sgml/ref/alter_subscription.sgml
index e4f0b6b16c7..13b413d142f 100644
--- a/doc/src/sgml/ref/alter_subscription.sgml
+++ b/doc/src/sgml/ref/alter_subscription.sgml
@@ -293,8 +293,9 @@ ALTER SUBSCRIPTION <replaceable class="parameter">name</replaceable> RENAME TO <
       <link linkend="sql-createsubscription-params-with-failover"><literal>failover</literal></link>,
       <link linkend="sql-createsubscription-params-with-two-phase"><literal>two_phase</literal></link>,
       <link linkend="sql-createsubscription-params-with-retain-dead-tuples"><literal>retain_dead_tuples</literal></link>,
-      <link linkend="sql-createsubscription-params-with-max-retention-duration"><literal>max_retention_duration</literal></link>, and
-      <link linkend="sql-createsubscription-params-with-wal-receiver-timeout"><literal>wal_receiver_timeout</literal></link>.
+      <link linkend="sql-createsubscription-params-with-max-retention-duration"><literal>max_retention_duration</literal></link>,
+      <link linkend="sql-createsubscription-params-with-wal-receiver-timeout"><literal>wal_receiver_timeout</literal></link>, and
+      <link linkend="sql-createsubscription-params-with-conflict-log-destination"><literal>conflict_log_destination</literal></link>.
       Only a superuser can set <literal>password_required = false</literal>.
      </para>
 
@@ -352,6 +353,14 @@ ALTER SUBSCRIPTION <replaceable class="parameter">name</replaceable> RENAME TO <
       <quote><literal>pg_conflict_detection</literal></quote>, created to retain
       dead tuples for conflict detection, will be dropped.
      </para>
+
+     <para>
+      When the <link linkend="sql-createsubscription-params-with-conflict-log-destination"><literal>conflict_log_destination</literal></link>
+      parameter is set to <literal>table</literal> or <literal>all</literal>, the system
+      automatically creates the conflict log table. Conversely, if the destination is changed to
+      <literal>log</literal>, logging to the table stops and the conflict log
+      table is automatically dropped.
+     </para>
     </listitem>
    </varlistentry>
 
diff --git a/doc/src/sgml/ref/create_subscription.sgml b/doc/src/sgml/ref/create_subscription.sgml
index 07d5b1bd77c..527f5ab874a 100644
--- a/doc/src/sgml/ref/create_subscription.sgml
+++ b/doc/src/sgml/ref/create_subscription.sgml
@@ -261,6 +261,57 @@ CREATE SUBSCRIPTION <replaceable class="parameter">subscription_name</replaceabl
         </listitem>
        </varlistentry>
 
+       <varlistentry id="sql-createsubscription-params-with-conflict-log-destination">
+        <term><literal>conflict_log_destination</literal> (<type>enum</type>)</term>
+        <listitem>
+         <para>
+          Specifies the destination for recording logical replication conflicts.
+         </para>
+         <para>
+          The available destinations are:
+          <itemizedlist>
+           <listitem>
+            <para>
+             <literal>log</literal>: Conflict details are recorded in the server log.
+             This is the default behavior. See
+             <xref linkend="logical-replication-conflict-file-based-logging"/>
+             for details.
+            </para>
+           </listitem>
+           <listitem>
+            <para>
+             <literal>table</literal>: The system automatically creates a structured table
+             named <literal>pg_conflict_log_for_subid_&lt;subid&gt;</literal>
+             in the <literal>pg_conflict</literal> schema. This allows for easy
+             querying and analysis of conflicts. See
+             <xref linkend="logical-replication-conflict-table-based-logging"/>
+             for details.
+            </para>
+            <caution>
+             <para>
+              The conflict log table is strictly tied to the lifecycle of the
+              subscription or the <literal>conflict_log_destination</literal> setting. If
+              the subscription is dropped, or if the destination is changed to
+              <literal>log</literal>, the table and all its recorded conflict data are
+              <emphasis>permanently deleted</emphasis>.
+             </para>
+             <para>
+              If conflict history may be needed later, back up the conflict log
+              table before it gets removed.
+             </para>
+            </caution>
+           </listitem>
+           <listitem>
+            <para>
+             <literal>all</literal>: Records conflict details to both destinations
+             <literal>log</literal> and <literal>table</literal>.
+            </para>
+           </listitem>
+          </itemizedlist>
+         </para>
+        </listitem>
+       </varlistentry>
+
        <varlistentry id="sql-createsubscription-params-with-copy-data">
         <term><literal>copy_data</literal> (<type>boolean</type>)</term>
         <listitem>
-- 
2.49.0

