From b6b02ae07051d8a57fad5cfa0d599ed2a228e4dc Mon Sep 17 00:00:00 2001
From: Ajin Cherian <itsajin@gmail.com>
Date: Mon, 7 Oct 2024 23:52:31 -0400
Subject: [PATCH v16 1/4] Add CONFLICT RESOLVERS into the syntax for CREATE and
 ALTER SUBSCRIPTION

This patch provides support for configuring subscriptions with conflict resolvers.

Syntax for CREATE SUBSCRIPTION:
CREATE SUBSCRIPTION <subname> CONNECTION <conninfo> PUBLICATION <pubname> CONFLICT RESOLVER
    (conflict_type1 = resolver1, conflict_type2 = resolver2, conflict_type3 = resolver3,...);

Syntax for ALTER SUBSCRIPTION:
ALTER SUBSCRIPTION <subname> CONFLICT RESOLVER
    (conflict_type1 = resolver1, conflict_type2 = resolver2, conflict_type3 = resolver3,...);

Syntax for resetting conflict resolvers:
 ALTER SUBSCRIPTION <subname> RESET CONFLICT RESOLVER ALL

 ALTER SUBSCRIPTION <subname> RESET CONFLICT RESOLVER FOR conflict_type

Additionally, this patch includes documentation for conflict resolvers.
---
 doc/src/sgml/logical-replication.sgml         |  95 +---
 doc/src/sgml/monitoring.sgml                  |  13 +-
 doc/src/sgml/ref/alter_subscription.sgml      |  62 +++
 doc/src/sgml/ref/create_subscription.sgml     | 206 +++++++++
 src/backend/commands/subscriptioncmds.c       |  53 +++
 src/backend/parser/gram.y                     |  49 +-
 src/backend/replication/logical/conflict.c    | 426 +++++++++++++++++-
 src/bin/pg_dump/pg_dump.c                     | 116 ++++-
 src/bin/pg_dump/pg_dump.h                     |   8 +
 src/bin/psql/describe.c                       |  15 +-
 src/include/catalog/Makefile                  |   3 +-
 src/include/catalog/meson.build               |   1 +
 .../catalog/pg_subscription_conflict.h        |  55 +++
 src/include/nodes/parsenodes.h                |   7 +
 src/include/parser/kwlist.h                   |   1 +
 src/include/replication/conflict.h            |  49 +-
 src/test/regress/expected/oidjoins.out        |   1 +
 src/test/regress/expected/subscription.out    | 251 +++++++----
 src/test/regress/sql/subscription.sql         |  63 +++
 src/tools/pgindent/typedefs.list              |   3 +
 20 files changed, 1303 insertions(+), 174 deletions(-)
 create mode 100644 src/include/catalog/pg_subscription_conflict.h

diff --git a/doc/src/sgml/logical-replication.sgml b/doc/src/sgml/logical-replication.sgml
index 98a7ad0c27..86c9b0b671 100644
--- a/doc/src/sgml/logical-replication.sgml
+++ b/doc/src/sgml/logical-replication.sgml
@@ -1568,7 +1568,7 @@ test_sub=# SELECT * FROM t1 ORDER BY id;
  </sect1>
 
  <sect1 id="logical-replication-conflicts">
-  <title>Conflicts</title>
+  <title>Conflicts and Conflict Resolution</title>
 
   <para>
    Logical replication behaves similarly to normal DML operations in that
@@ -1582,86 +1582,19 @@ test_sub=# SELECT * FROM t1 ORDER BY id;
   </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-missing" xreflabel="update_missing">
-     <term><literal>update_missing</literal></term>
-     <listitem>
-      <para>
-       The tuple 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 tuple to be deleted was not found. The delete will simply be
-       skipped in this scenario.
-      </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.
+   There are various conflict scenarios, each identified as a <firstterm>conflict type</firstterm>.
+   Users can configure a <firstterm>conflict resolver</firstterm> for each
+   conflict type when creating a subscription. For more information, refer to
+   <link linkend="sql-createsubscription-params-with-conflict-resolver">
+   <command>CREATE SUBSCRIPTION ... CONFLICT RESOLVER</command></link>.
+  </para>
+  <para>
+   When a conflict occurs the details about it are logged, and the conflict
+   statistics are recorded in the <link linkend="monitoring-pg-stat-subscription-stats">
+   <structname>pg_stat_subscription_stats</structname></link> view.
+   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>
 
   <para>
diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 331315f8d3..d79db76e16 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -2181,7 +2181,7 @@ description | Waiting for a newly initialized WAL file to reach durable storage
       <para>
        Number of times a row insertion violated a
        <literal>NOT DEFERRABLE</literal> unique constraint during the
-       application of changes. See <xref linkend="conflict-insert-exists"/>
+       application of changes. See <xref linkend="sql-createsubscription-params-with-conflict_type-insert-exists"/>
        for details about this conflict.
       </para></entry>
      </row>
@@ -2193,7 +2193,7 @@ description | Waiting for a newly initialized WAL file to reach durable storage
       <para>
        Number of times an update was applied to a row that had been previously
        modified by another source during the application of changes. See
-       <xref linkend="conflict-update-origin-differs"/> for details about this
+       <xref linkend="sql-createsubscription-params-with-conflict_type-update-origin-differs"/> for details about this
        conflict.
       </para></entry>
      </row>
@@ -2205,7 +2205,7 @@ description | Waiting for a newly initialized WAL file to reach durable storage
       <para>
        Number of times that an updated row value violated a
        <literal>NOT DEFERRABLE</literal> unique constraint during the
-       application of changes. See <xref linkend="conflict-update-exists"/>
+       application of changes. See <xref linkend="sql-createsubscription-params-with-conflict_type-update-exists"/>
        for details about this conflict.
       </para></entry>
      </row>
@@ -2216,7 +2216,8 @@ description | Waiting for a newly initialized WAL file to reach durable storage
       </para>
       <para>
        Number of times the tuple to be updated was not found during the
-       application of changes. See <xref linkend="conflict-update-missing"/>
+       application of changes. See <xref linkend="sql-createsubscription-params-with-conflict_type-update-missing
+"/>
        for details about this conflict.
       </para></entry>
      </row>
@@ -2228,7 +2229,7 @@ description | Waiting for a newly initialized WAL file to reach durable storage
       <para>
        Number of times a delete operation was applied to row that had been
        previously modified by another source during the application of changes.
-       See <xref linkend="conflict-delete-origin-differs"/> for details about
+       See <xref linkend="sql-createsubscription-params-with-conflict_type-delete-origin-differs"/> for details about
        this conflict.
       </para></entry>
      </row>
@@ -2239,7 +2240,7 @@ description | Waiting for a newly initialized WAL file to reach durable storage
       </para>
       <para>
        Number of times the tuple to be deleted was not found during the application
-       of changes. See <xref linkend="conflict-delete-missing"/> for details
+       of changes. See <xref linkend="sql-createsubscription-params-with-conflict_type-delete-missing"/> for details
        about this conflict.
       </para></entry>
      </row>
diff --git a/doc/src/sgml/ref/alter_subscription.sgml b/doc/src/sgml/ref/alter_subscription.sgml
index fdc648d007..e97edfecba 100644
--- a/doc/src/sgml/ref/alter_subscription.sgml
+++ b/doc/src/sgml/ref/alter_subscription.sgml
@@ -32,6 +32,9 @@ ALTER SUBSCRIPTION <replaceable class="parameter">name</replaceable> SET ( <repl
 ALTER SUBSCRIPTION <replaceable class="parameter">name</replaceable> SKIP ( <replaceable class="parameter">skip_option</replaceable> = <replaceable class="parameter">value</replaceable> )
 ALTER SUBSCRIPTION <replaceable class="parameter">name</replaceable> OWNER TO { <replaceable>new_owner</replaceable> | CURRENT_ROLE | CURRENT_USER | SESSION_USER }
 ALTER SUBSCRIPTION <replaceable class="parameter">name</replaceable> RENAME TO <replaceable>new_name</replaceable>
+ALTER SUBSCRIPTION <replaceable class="parameter">name</replaceable> CONFLICT RESOLVER ( <replaceable class="parameter">conflict_type</replaceable> = <replaceable class="parameter">conflict_resolver</replaceable> [, ...] )
+ALTER SUBSCRIPTION <replaceable class="parameter">name</replaceable> RESET CONFLICT RESOLVER ALL
+ALTER SUBSCRIPTION <replaceable class="parameter">name</replaceable> RESET CONFLICT RESOLVER FOR '<replaceable class="parameter">conflict_type</replaceable>'
 </synopsis>
  </refsynopsisdiv>
 
@@ -345,6 +348,65 @@ ALTER SUBSCRIPTION <replaceable class="parameter">name</replaceable> RENAME TO <
      </para>
     </listitem>
    </varlistentry>
+
+   <varlistentry id="sql-altersubscription-params-conflict-resolver">
+    <term><literal>CONFLICT RESOLVER ( <replaceable class="parameter">conflict_type</replaceable> = <replaceable class="parameter">conflict_resolver</replaceable> [, ... ] )</literal></term>
+    <listitem>
+     <para>
+      This clause alters the current conflict resolver for the specified conflict types.
+      Refer to <link linkend="sql-createsubscription-params-with-conflict-resolver">
+      <command>CREATE SUBSCRIBER ... CONFLICT RESOLVER</command></link>
+      for details about different <literal>conflict_type</literal> and what
+      kind of <literal>conflict_resolver</literal> can be assigned to them.
+     </para>
+     <variablelist>
+      <varlistentry id="sql-altersubscription-params-conflict-resolver-type">
+       <term><replaceable class="parameter">conflict_type</replaceable></term>
+       <listitem>
+        <para>
+         The conflict type.
+        </para>
+       </listitem>
+      </varlistentry>
+      <varlistentry id="sql-altersubscription-params-conflict-resolver-resolver">
+       <term><replaceable class="parameter">conflict_resolver</replaceable></term>
+       <listitem>
+        <para>
+         The conflict resolver to use for this conflict type.
+        </para>
+       </listitem>
+      </varlistentry>
+     </variablelist>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry id="sql-altersubscription-params-reset-conflict-resolver">
+    <term><literal>RESET CONFLICT RESOLVER</literal></term>
+    <listitem>
+     <para>
+     Conflict types can either be reset to their default resolvers all at once using <replaceable class="parameter">ALL</replaceable>, or a specific conflict type can be reset using <replaceable class="parameter">FOR 'conflict_type'</replaceable>.
+     For details on conflict types and their default resolvers, refer to section <link linkend="sql-createsubscription-params-with-conflict-resolver"><literal>CREATE SUBSCRIBER ... CONFLICT RESOLVER</literal></link>.
+     </para>
+     <variablelist>
+      <varlistentry id="sql-altersubscription-params-reset-all">
+       <term><literal>ALL</literal></term>
+       <listitem>
+        <para>
+         All conflict types will be reset to their respective default resolvers.
+        </para>
+       </listitem>
+      </varlistentry>
+      <varlistentry id="sql-altersubscription-params-reset-conflict-type">
+       <term><literal>FOR</literal> <replaceable class="parameter">conflict_type</replaceable></term>
+       <listitem>
+        <para>
+         The given <replaceable class="parameter">conflict_type</replaceable> will be reset to its default resolver.
+        </para>
+       </listitem>
+      </varlistentry>
+     </variablelist>
+    </listitem>
+   </varlistentry>
   </variablelist>
 
   <para>
diff --git a/doc/src/sgml/ref/create_subscription.sgml b/doc/src/sgml/ref/create_subscription.sgml
index 8a3096e62b..91d5ffbab4 100644
--- a/doc/src/sgml/ref/create_subscription.sgml
+++ b/doc/src/sgml/ref/create_subscription.sgml
@@ -24,6 +24,7 @@ PostgreSQL documentation
 CREATE SUBSCRIPTION <replaceable class="parameter">subscription_name</replaceable>
     CONNECTION '<replaceable class="parameter">conninfo</replaceable>'
     PUBLICATION <replaceable class="parameter">publication_name</replaceable> [, ...]
+    [ CONFLICT RESOLVER ( <replaceable class="parameter">conflict_type</replaceable> = <replaceable class="parameter">conflict_resolver</replaceable> [, ...] ) ]
     [ WITH ( <replaceable class="parameter">subscription_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) ]
 </synopsis>
  </refsynopsisdiv>
@@ -97,6 +98,211 @@ CREATE SUBSCRIPTION <replaceable class="parameter">subscription_name</replaceabl
     </listitem>
    </varlistentry>
 
+   <varlistentry id="sql-createsubscription-params-with-conflict-resolver">
+    <term><literal>CONFLICT RESOLVER ( <replaceable class="parameter">conflict_type</replaceable> = <replaceable class="parameter">conflict_resolver</replaceable> [, ... ] )</literal></term>
+    <listitem>
+     <para>
+      This optional clause specifies conflict resolvers for different conflict_types.
+     </para>
+
+     <para>
+      The default behavior for each <replaceable class="parameter">conflict_type</replaceable> is listed below.
+      <variablelist>
+       <varlistentry id="sql-createsubscription-params-with-conflict_type-insert-exists" xreflabel="insert_exists">
+        <term><literal>insert_exists</literal> (<type>enum</type>)</term>
+        <listitem>
+         <para>
+          This conflict occurs when inserting a row that violates a
+          <literal>NOT DEFERRABLE</literal> unique constraint.
+          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 or the resolver is configured to a
+          non-default value that can automatically resolve the conflict.
+         </para>
+        </listitem>
+       </varlistentry>
+
+      <varlistentry id="sql-createsubscription-params-with-conflict_type-update-exists" xreflabel="update_exists">
+       <term><literal>update_exists</literal> (<type>enum</type>)</term>
+       <listitem>
+        <para>
+         This conflict occurs when the updated value of a row violates
+         a <literal>NOT DEFERRABLE</literal>
+         unique constraint. 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 or the resolver is configured to a
+         non-default value that can automatically resolve the conflict.
+         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>
+        <warning>
+         <para>
+          If the local table contains multiple <literal>NOT DEFERRABLE</literal> unique constraint
+          columns and the conflict resolution strategy for an INSERT or UPDATE favors applying the changes,
+          then a remote INSERT or UPDATE could trigger multiple <literal>update_exists</literal> conflicts.
+          Each conflict will be detected and resolved in sequence, potentially leading to the deletion of
+          multiple local rows.
+         </para>
+        </warning>
+       </listitem>
+      </varlistentry>
+
+      <varlistentry id="sql-createsubscription-params-with-conflict_type-update-missing" xreflabel="update_missing">
+       <term><literal>update_missing</literal> (<type>enum</type>)</term>
+       <listitem>
+        <para>
+         This conflict occurs when the tuple to be updated was not found.
+         The update will simply be skipped in this scenario.
+        </para>
+       </listitem>
+      </varlistentry>
+
+       <varlistentry id="sql-createsubscription-params-with-conflict_type-update-origin-differs" xreflabel="update_origin_differs">
+        <term><literal>update_origin_differs</literal> (<type>enum</type>)</term>
+        <listitem>
+         <para>
+          This conflict occurs when 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="sql-createsubscription-params-with-conflict_type-delete-missing" xreflabel="delete_missing">
+       <term><literal>delete_missing</literal> (<type>enum</type>)</term>
+       <listitem>
+        <para>
+         This conflict occurs when the tuple to be deleted was not found.
+         The delete will simply be skipped in this scenario.
+        </para>
+       </listitem>
+      </varlistentry>
+
+      <varlistentry id="sql-createsubscription-params-with-conflict_type-delete-origin-differs" xreflabel="delete_origin_differs">
+       <term><literal>delete_origin_differs</literal> (<type>enum</type>)</term>
+       <listitem>
+        <para>
+         This conflict occurs when 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>
+
+     </variablelist>
+    </para>
+
+    <para>
+     The behavior of each <replaceable class="parameter">conflict_resolver</replaceable>
+     is described below. Users can choose from the following resolvers for automatic conflict
+     resolution.
+     <variablelist>
+
+      <varlistentry id="sql-createsubscription-params-with-conflict_resolver-error">
+       <term><literal>error</literal> (<type>enum</type>)</term>
+       <listitem>
+        <para>
+         This resolver throws an error and stops replication. It can be used for
+         any conflict type.
+         It is the default resolver for <literal>insert_exists</literal> and
+         <literal>update_exists</literal>.
+        </para>
+       </listitem>
+      </varlistentry>
+
+      <varlistentry id="sql-createsubscription-params-with-conflict_resolver-skip">
+       <term><literal>skip</literal> (<type>enum</type>)</term>
+       <listitem>
+        <para>
+         This resolver skips processing the remote change and continue replication
+         with the next change.
+         It can be used for <literal>update_missing</literal> and
+         <literal>delete_missing</literal> and is the default resolver for these types.
+        </para>
+       </listitem>
+      </varlistentry>
+
+      <varlistentry id="sql-createsubscription-params-with-conflict_resolver-apply-remote">
+       <term><literal>apply_remote</literal> (<type>enum</type>)</term>
+       <listitem>
+        <para>
+         This resolver applies the remote change. It can be used for
+         <literal>insert_exists</literal>, <literal>update_exists</literal>,
+         <literal>update_origin_differs</literal> and <literal>delete_origin_differs</literal>.
+         It is the default resolver for <literal>update_origin_differs</literal> and
+         <literal>delete_origin_differs</literal>.
+        </para>
+       </listitem>
+      </varlistentry>
+
+      <varlistentry id="sql-createsubscription-params-with-conflict_resolver-keep-local">
+       <term><literal>keep_local</literal> (<type>enum</type>)</term>
+        <listitem>
+         <para>
+          With this resolver, the remote change is not applied and the local tuple is maintained.
+          It can be used for <literal>insert_exists</literal>, <literal>update_exists</literal>,
+          <literal>update_origin_differs</literal> and <literal>delete_origin_differs</literal>.
+         </para>
+        </listitem>
+       </varlistentry>
+
+      <varlistentry id="sql-createsubscription-params-with-conflict_resolver-apply-or-error">
+       <term><literal>apply_or_error</literal> (<type>enum</type>)</term>
+       <listitem>
+        <para>
+         This resolver is only used for <literal>update_missing</literal>.
+         An attempt is made to convert the update into an insert; if this cannot
+         be done due to missing information, then an error is thrown, and replication
+         is stopped.
+        </para>
+       </listitem>
+      </varlistentry>
+
+      <varlistentry id="sql-createsubscription-params-with-conflict_resolver-apply-or-skip">
+       <term><literal>apply_or_skip</literal> (<type>enum</type>)</term>
+       <listitem>
+        <para>
+         This resolver is only used for <literal>update_missing</literal>.
+         An attempt is made to convert the update into an insert; if this
+         cannot be done due to missing information, then the change is skipped.
+        </para>
+       </listitem>
+      </varlistentry>
+
+      </variablelist>
+     </para>
+
+     <table id="sql-createsubscription-params-conflict-type-resolver-summary">
+      <title>Conflict type/resolver Summary</title>
+      <tgroup cols="3">
+       <thead>
+        <row><entry>Conflict type</entry>        <entry>Default resolver</entry>  <entry>Possible resolvers</entry></row>
+       </thead>
+       <tbody>
+        <row><entry>insert_exists</entry>        <entry>error</entry>             <entry>apply_remote, error, keep_local</entry></row>
+        <row><entry>update_exists</entry>        <entry>error</entry>             <entry>apply_remote, error, keep_local</entry></row>
+        <row><entry>update_missing</entry>       <entry>skip</entry>              <entry>apply_or_error, apply_or_skip, error, skip</entry></row>
+        <row><entry>update_origin_differs</entry><entry>apply_remote</entry>      <entry>apply_remote, error, keep_local</entry></row>
+        <row><entry>delete_missing</entry>       <entry>skip</entry>              <entry>error, skip</entry></row>
+        <row><entry>delete_origin_differs</entry><entry>apply_remote</entry>      <entry>apply_remote, error, keep_local</entry></row>
+       </tbody>
+      </tgroup>
+     </table>
+
+    </listitem>
+   </varlistentry>
+
    <varlistentry id="sql-createsubscription-params-with">
     <term><literal>WITH ( <replaceable class="parameter">subscription_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )</literal></term>
     <listitem>
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 02ccc636b8..54d8ad9554 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -36,6 +36,7 @@
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
+#include "parser/scansup.h"
 #include "pgstat.h"
 #include "replication/logicallauncher.h"
 #include "replication/logicalworker.h"
@@ -583,6 +584,7 @@ CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt,
 	bits32		supported_opts;
 	SubOpts		opts = {0};
 	AclResult	aclresult;
+	List	   *conflict_resolvers = NIL;
 
 	/*
 	 * Parse and check options.
@@ -597,6 +599,10 @@ CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt,
 					  SUBOPT_RUN_AS_OWNER | SUBOPT_FAILOVER | SUBOPT_ORIGIN);
 	parse_subscription_options(pstate, stmt->options, supported_opts, &opts);
 
+	/* Parse and get conflict resolvers list. */
+	conflict_resolvers =
+		ParseAndGetSubConflictResolvers(pstate, stmt->resolvers, true);
+
 	/*
 	 * Since creating a replication slot is not transactional, rolling back
 	 * the transaction leaves the created replication slot.  So we cannot run
@@ -723,6 +729,9 @@ CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt,
 
 	recordDependencyOnOwner(SubscriptionRelationId, subid, owner);
 
+	/* Update the Conflict Resolvers in pg_subscription_conflict */
+	SetSubConflictResolvers(subid, conflict_resolvers);
+
 	ReplicationOriginNameForLogicalRep(subid, InvalidOid, originname, sizeof(originname));
 	replorigin_create(originname);
 
@@ -1581,6 +1590,47 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
 				update_tuple = true;
 				break;
 			}
+		case ALTER_SUBSCRIPTION_CONFLICT_RESOLVERS:
+			{
+				List	   *conflict_resolvers = NIL;
+
+				/*
+				 * Get the list of conflict types and resolvers and validate
+				 * them.
+				 */
+				conflict_resolvers = ParseAndGetSubConflictResolvers(
+																	 pstate,
+																	 stmt->resolvers,
+																	 false);
+
+				/*
+				 * Update the conflict resolvers for the corresponding
+				 * conflict types in the pg_subscription_conflict catalog.
+				 */
+				UpdateSubConflictResolvers(conflict_resolvers, subid);
+				break;
+			}
+		case ALTER_SUBSCRIPTION_CONFLICT_RESOLVER_RESET_ALL:
+			{
+				List	   *conflict_resolvers = NIL;
+
+				/*
+				 * Create list of conflict resolvers and set them in the
+				 * catalog.
+				 */
+				conflict_resolvers = GetDefaultConflictResolvers();
+				UpdateSubConflictResolvers(conflict_resolvers, subid);
+				break;
+			}
+		case ALTER_SUBSCRIPTION_CONFLICT_RESOLVER_RESET:
+			{
+				/*
+				 * Reset the conflict resolver for this conflict type to its
+				 * default.
+				 */
+				ResetSubConflictResolver(subid, stmt->conflict_type_name);
+				break;
+			}
 
 		default:
 			elog(ERROR, "unrecognized ALTER SUBSCRIPTION kind %d",
@@ -1832,6 +1882,9 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
 	/* Remove any associated relation synchronization states. */
 	RemoveSubscriptionRel(subid, InvalidOid);
 
+	/* Remove any associated conflict resolvers */
+	RemoveSubConflictResolvers(subid);
+
 	/* Remove the origin tracking if exists. */
 	ReplicationOriginNameForLogicalRep(subid, InvalidOid, originname, sizeof(originname));
 	replorigin_drop_by_name(originname, true, false);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 4aa8646af7..28591c90c9 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -425,7 +425,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 				OptTableElementList TableElementList OptInherit definition
 				OptTypedTableElementList TypedTableElementList
 				reloptions opt_reloptions
-				OptWith opt_definition func_args func_args_list
+				OptWith opt_definition opt_resolver_definition func_args func_args_list
 				func_args_with_defaults func_args_with_defaults_list
 				aggr_args aggr_args_list
 				func_as createfunc_opt_list opt_createfunc_opt_list alterfunc_opt_list
@@ -613,6 +613,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <ival>	opt_check_option
 
 %type <str>		opt_provider security_label
+%type <str>		conflict_type
 
 %type <target>	xml_attribute_el
 %type <list>	xml_attribute_list xml_attributes
@@ -770,7 +771,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
 	RANGE READ REAL REASSIGN RECURSIVE REF_P REFERENCES REFERENCING
 	REFRESH REINDEX RELATIVE_P RELEASE RENAME REPEATABLE REPLACE REPLICA
-	RESET RESTART RESTRICT RETURN RETURNING RETURNS REVOKE RIGHT ROLE ROLLBACK ROLLUP
+	RESET RESOLVER RESTART RESTRICT RETURN RETURNING RETURNS REVOKE RIGHT ROLE ROLLBACK ROLLUP
 	ROUTINE ROUTINES ROW ROWS RULE
 
 	SAVEPOINT SCALAR SCHEMA SCHEMAS SCROLL SEARCH SECOND_P SECURITY SELECT
@@ -8813,6 +8814,11 @@ opt_definition:
 			| /*EMPTY*/								{ $$ = NIL; }
 		;
 
+opt_resolver_definition:
+			CONFLICT RESOLVER definition			{ $$ = $3; }
+			| /*EMPTY*/								{ $$ = NIL; }
+		;
+
 table_func_column:	param_name func_type
 				{
 					FunctionParameter *n = makeNode(FunctionParameter);
@@ -10747,14 +10753,15 @@ AlterPublicationStmt:
  *****************************************************************************/
 
 CreateSubscriptionStmt:
-			CREATE SUBSCRIPTION name CONNECTION Sconst PUBLICATION name_list opt_definition
+	CREATE SUBSCRIPTION name CONNECTION Sconst PUBLICATION name_list opt_resolver_definition opt_definition
 				{
 					CreateSubscriptionStmt *n =
 						makeNode(CreateSubscriptionStmt);
 					n->subname = $3;
 					n->conninfo = $5;
 					n->publication = $7;
-					n->options = $8;
+					n->resolvers = $8;
+					n->options = $9;
 					$$ = (Node *) n;
 				}
 		;
@@ -10861,6 +10868,38 @@ AlterSubscriptionStmt:
 					n->options = $5;
 					$$ = (Node *) n;
 				}
+			| ALTER SUBSCRIPTION name opt_resolver_definition
+				{
+					AlterSubscriptionStmt *n =
+						makeNode(AlterSubscriptionStmt);
+
+					n->kind = ALTER_SUBSCRIPTION_CONFLICT_RESOLVERS;
+					n->subname = $3;
+					n->resolvers = $4;
+					$$ = (Node *) n;
+				}
+			| ALTER SUBSCRIPTION name RESET CONFLICT RESOLVER ALL
+				{
+					AlterSubscriptionStmt *n =
+						makeNode(AlterSubscriptionStmt);
+
+					n->kind = ALTER_SUBSCRIPTION_CONFLICT_RESOLVER_RESET_ALL;
+					n->subname = $3;
+					$$ = (Node *) n;
+				}
+			| ALTER SUBSCRIPTION name RESET CONFLICT RESOLVER FOR conflict_type
+				{
+					AlterSubscriptionStmt *n =
+						makeNode(AlterSubscriptionStmt);
+
+					n->kind = ALTER_SUBSCRIPTION_CONFLICT_RESOLVER_RESET;
+					n->subname = $3;
+					n->conflict_type_name = $8;
+					$$ = (Node *) n;
+				}
+		;
+conflict_type:
+			Sconst								{ $$ = $1; }
 		;
 
 /*****************************************************************************
@@ -17785,6 +17824,7 @@ unreserved_keyword:
 			| REPLACE
 			| REPLICA
 			| RESET
+			| RESOLVER
 			| RESTART
 			| RESTRICT
 			| RETURN
@@ -18414,6 +18454,7 @@ bare_label_keyword:
 			| REPLACE
 			| REPLICA
 			| RESET
+			| RESOLVER
 			| RESTART
 			| RESTRICT
 			| RETURN
diff --git a/src/backend/replication/logical/conflict.c b/src/backend/replication/logical/conflict.c
index 5d9ff626bd..eb46e39013 100644
--- a/src/backend/replication/logical/conflict.c
+++ b/src/backend/replication/logical/conflict.c
@@ -8,20 +8,28 @@
  *	  src/backend/replication/logical/conflict.c
  *
  * This file contains the code for logging conflicts on the subscriber during
- * logical replication.
+ * logical replication and setting up conflict resolvers for a subscription.
  *-------------------------------------------------------------------------
  */
 
 #include "postgres.h"
 
 #include "access/commit_ts.h"
+#include "access/heapam.h"
+#include "catalog/indexing.h"
+#include "catalog/pg_subscription_conflict_d.h"
+#include "commands/defrem.h"
 #include "access/tableam.h"
 #include "executor/executor.h"
+#include "parser/scansup.h"
 #include "pgstat.h"
 #include "replication/conflict.h"
+#include "utils/builtins.h"
+#include "utils/fmgroids.h"
 #include "replication/worker_internal.h"
 #include "storage/lmgr.h"
 #include "utils/lsyscache.h"
+#include "utils/syscache.h"
 
 static const char *const ConflictTypeNames[] = {
 	[CT_INSERT_EXISTS] = "insert_exists",
@@ -32,6 +40,50 @@ static const char *const ConflictTypeNames[] = {
 	[CT_DELETE_MISSING] = "delete_missing"
 };
 
+static const char *const ConflictResolverNames[] = {
+	[CR_APPLY_REMOTE] = "apply_remote",
+	[CR_KEEP_LOCAL] = "keep_local",
+	[CR_APPLY_OR_SKIP] = "apply_or_skip",
+	[CR_APPLY_OR_ERROR] = "apply_or_error",
+	[CR_SKIP] = "skip",
+	[CR_ERROR] = "error"
+};
+
+#define CONFLICT_TYPE_MAX_RESOLVERS 4
+
+/*
+ * Valid conflict resolvers for each conflict type.
+ *
+ * First member represents default resolver for each conflict_type.
+ * The same defaults are used in pg_dump.c. If any default is changed here,
+ * ensure the corresponding value is updated in pg_dump's is_default_resolver
+ * function.
+ *
+ * XXX: If we do not want to maintain different resolvers such as
+ * apply_or_skip and apply_or_error for update_missing conflict,
+ * then we can retain apply_remote and keep_local only. Then these
+ * resolvers in context of update_missing will mean:
+ *
+ * keep_local: do not apply the update as INSERT.
+ * apply_remote: apply the update as INSERT. If we could not apply,
+ * then log and skip.
+ *
+ * Similarly SKIP can be replaced with KEEP_LOCAL for both update_missing
+ * and delete_missing conflicts. For missing rows, 'SKIP' sounds more user
+ * friendly name for a resolver and thus has been added here.
+ */
+static const int ConflictTypeResolverMap[][CONFLICT_NUM_RESOLVERS + 1] = {
+	[CT_INSERT_EXISTS] = {CR_ERROR, CR_APPLY_REMOTE, CR_KEEP_LOCAL, -1},
+	[CT_UPDATE_EXISTS] = {CR_ERROR, CR_APPLY_REMOTE, CR_KEEP_LOCAL, -1},
+	[CT_UPDATE_ORIGIN_DIFFERS] = {CR_APPLY_REMOTE, CR_KEEP_LOCAL, CR_ERROR, -1},
+	[CT_UPDATE_MISSING] = {CR_SKIP, CR_APPLY_OR_SKIP, CR_APPLY_OR_ERROR, CR_ERROR, -1},
+	[CT_DELETE_MISSING] = {CR_SKIP, CR_ERROR, -1},
+	[CT_DELETE_ORIGIN_DIFFERS] = {CR_APPLY_REMOTE, CR_KEEP_LOCAL, CR_ERROR, -1}
+};
+
+StaticAssertDecl(lengthof(ConflictTypeResolverMap) == CONFLICT_NUM_TYPES,
+				 "array length mismatch");
+
 static int	errcode_apply_conflict(ConflictType type);
 static int	errdetail_apply_conflict(EState *estate,
 									 ResultRelInfo *relinfo,
@@ -489,3 +541,375 @@ build_index_value_desc(EState *estate, Relation localrel, TupleTableSlot *slot,
 
 	return index_value;
 }
+
+/*
+ * Report a warning about incomplete conflict detection and resolution if
+ * track_commit_timestamp is disabled.
+ */
+static void
+conf_detection_check_prerequisites(void)
+{
+	if (!track_commit_timestamp)
+		ereport(WARNING,
+				errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				errmsg("conflict detection and resolution could be incomplete due to disabled track_commit_timestamp"),
+				errdetail("Conflicts update_origin_differs and delete_origin_differs cannot be "
+						  "detected, and the origin and commit timestamp for the local row "
+						  "will not be logged."));
+}
+
+/*
+ * Validate the conflict type and return the corresponding ConflictType enum.
+ */
+ConflictType
+ValidateConflictType(const char *conflict_type)
+{
+	ConflictType type;
+	bool		valid = false;
+
+	/* Check conflict type validity */
+	for (type = 0; type < CONFLICT_NUM_TYPES; type++)
+	{
+		if (pg_strcasecmp(ConflictTypeNames[type], conflict_type) == 0)
+		{
+			valid = true;
+			break;
+		}
+	}
+
+	if (!valid)
+		ereport(ERROR,
+				errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				errmsg("%s is not a valid conflict type", conflict_type));
+
+	return type;
+}
+
+/*
+ * Validate the conflict type and resolver. It returns an enum ConflictType
+ * corresponding to the conflict type string passed by the caller.
+ */
+ConflictType
+ValidateConflictTypeAndResolver(const char *conflict_type,
+								const char *conflict_resolver)
+{
+	ConflictType type;
+	ConflictResolver resolver;
+	bool		valid = false;
+
+	/* Validate conflict type */
+	type = ValidateConflictType(conflict_type);
+
+	/* Validate the conflict resolver name */
+	for (resolver = 0; resolver < CONFLICT_NUM_RESOLVERS; resolver++)
+	{
+		if (pg_strcasecmp(ConflictResolverNames[resolver], conflict_resolver) == 0)
+		{
+			valid = true;
+			break;
+		}
+	}
+
+	if (!valid)
+	{
+		ereport(ERROR,
+				errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				errmsg("%s is not a valid conflict resolver", conflict_resolver));
+	}
+
+	/* Reset */
+	valid = false;
+
+	/* Check if conflict resolver is a valid one for the given conflict type */
+	for (int i = 0; i < CONFLICT_NUM_RESOLVERS; i++)
+	{
+		int			candidate = ConflictTypeResolverMap[type][i];
+
+		if (candidate < 0)
+			/* No more possible resolvers for this conflict type */
+			break;
+
+		if (candidate == resolver)
+		{
+			valid = true;
+			break;
+		}
+	}
+
+	if (!valid)
+		ereport(ERROR,
+				errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				errmsg("%s is not a valid conflict resolver for conflict type %s",
+					   conflict_resolver,
+					   conflict_type));
+
+	return type;
+}
+
+/*
+ * Common 'CONFLICT RESOLVER' parsing function for CREATE and ALTER
+ * SUBSCRIPTION commands.
+ *
+ * It reports an error if duplicate options are specified.
+ *
+ * Returns a list of conflict types along with their corresponding conflict
+ * resolvers. If 'add_defaults' is true, it appends default resolvers for any
+ * conflict types that have not been explicitly defined by the user.
+ */
+List *
+ParseAndGetSubConflictResolvers(ParseState *pstate,
+								List *stmtresolvers, bool add_defaults)
+{
+	List	   *SeenTypes = NIL;
+	List	   *res = NIL;
+
+	/* Loop through the user provided resolvers */
+	foreach_ptr(DefElem, defel, stmtresolvers)
+	{
+		char	   *resolver;
+		ConflictTypeResolver *conftyperesolver = NULL;
+
+		/* Check if the conflict type has already been seen */
+		if (list_member(SeenTypes, makeString(defel->defname)))
+			errorConflictingDefElem(defel, pstate);
+
+		/* Validate the conflict type and resolver */
+		resolver = defGetString(defel);
+		resolver = downcase_truncate_identifier(
+												resolver, strlen(resolver), false);
+		ValidateConflictTypeAndResolver(defel->defname,
+										resolver);
+
+		/* Add the conflict type to the list of seen types */
+		SeenTypes = lappend(SeenTypes, makeString(defel->defname));
+
+		conftyperesolver = palloc(sizeof(ConflictTypeResolver));
+		conftyperesolver->conflict_type_name = defel->defname;
+		conftyperesolver->conflict_resolver_name = resolver;
+		res = lappend(res, conftyperesolver);
+	}
+
+	/* Once validation is complete, warn users if prerequisites are not met */
+	if (stmtresolvers)
+		conf_detection_check_prerequisites();
+
+	/*
+	 * If add_defaults is true, fill remaining conflict types with default
+	 * resolvers.
+	 */
+	if (add_defaults)
+	{
+		for (int i = 0; i < CONFLICT_NUM_TYPES; i++)
+		{
+			ConflictTypeResolver *conftyperesolver = NULL;
+
+			if (!list_member(SeenTypes, makeString((char *) ConflictTypeNames[i])))
+			{
+				ConflictResolver def_resolver = ConflictTypeResolverMap[i][0];
+
+				conftyperesolver = palloc(sizeof(ConflictTypeResolver));
+				conftyperesolver->conflict_type_name = ConflictTypeNames[i];
+				conftyperesolver->conflict_resolver_name = ConflictResolverNames[def_resolver];;
+				res = lappend(res, conftyperesolver);
+			}
+		}
+	}
+
+	list_free_deep(SeenTypes);
+
+	return res;
+}
+
+/*
+ * Get the list of conflict types and their corresponding default resolvers.
+ */
+List *
+GetDefaultConflictResolvers()
+{
+	List	   *res = NIL;
+
+	for (ConflictType type = 0; type < CONFLICT_NUM_TYPES; type++)
+	{
+		ConflictTypeResolver *resolver = NULL;
+		ConflictResolver def_resolver = ConflictTypeResolverMap[type][0];
+
+		/* Allocate memory for each ConflictTypeResolver */
+		resolver = palloc(sizeof(ConflictTypeResolver));
+
+		resolver->conflict_type_name = ConflictTypeNames[type];
+		resolver->conflict_resolver_name = ConflictResolverNames[def_resolver];
+
+		/* Append to the response list */
+		res = lappend(res, resolver);
+	}
+
+	return res;
+}
+
+/*
+ * Update the Subscription's conflict resolvers in pg_subscription_conflict
+ * system catalog for the given conflict types.
+ */
+void
+UpdateSubConflictResolvers(List *conflict_resolvers, Oid subid)
+{
+	Datum		values[Natts_pg_subscription_conflict];
+	bool		nulls[Natts_pg_subscription_conflict];
+	bool		replaces[Natts_pg_subscription_conflict];
+	HeapTuple	oldtup;
+	HeapTuple	newtup = NULL;
+	Relation	pg_subscription_conflict;
+	char	   *cur_conflict_res;
+	Datum		datum;
+
+	/* Prepare to update a tuple */
+	memset(nulls, false, sizeof(nulls));
+	memset(replaces, false, sizeof(replaces));
+	memset(values, 0, sizeof(values));
+
+	pg_subscription_conflict = table_open(SubscriptionConflictId, RowExclusiveLock);
+
+	foreach_ptr(ConflictTypeResolver, conftyperesolver, conflict_resolvers)
+	{
+		/* Set up subid and conflict_type to search in cache */
+		values[Anum_pg_subscription_conflict_confsubid - 1] = ObjectIdGetDatum(subid);
+		values[Anum_pg_subscription_conflict_conftype - 1] =
+			CStringGetTextDatum(conftyperesolver->conflict_type_name);
+
+		oldtup = SearchSysCache2(SUBSCRIPTIONCONFLMAP,
+								 values[Anum_pg_subscription_conflict_confsubid - 1],
+								 values[Anum_pg_subscription_conflict_conftype - 1]);
+
+		if (!HeapTupleIsValid(oldtup))
+			elog(ERROR, "cache lookup failed for table conflict %s for subid %u",
+				 conftyperesolver->conflict_type_name, subid);
+
+		datum = SysCacheGetAttrNotNull(SUBSCRIPTIONCONFLMAP,
+									   oldtup, Anum_pg_subscription_conflict_confres);
+		cur_conflict_res = TextDatumGetCString(datum);
+
+		/*
+		 * Update system catalog only if the new resolver is not same as the
+		 * existing one.
+		 */
+		if (pg_strcasecmp(cur_conflict_res,
+						  conftyperesolver->conflict_resolver_name) != 0)
+		{
+			/* Update the new resolver */
+			values[Anum_pg_subscription_conflict_confres - 1] =
+				CStringGetTextDatum(conftyperesolver->conflict_resolver_name);
+			replaces[Anum_pg_subscription_conflict_confres - 1] = true;
+
+			newtup = heap_modify_tuple(oldtup,
+									   RelationGetDescr(pg_subscription_conflict),
+									   values, nulls, replaces);
+			CatalogTupleUpdate(pg_subscription_conflict,
+							   &oldtup->t_self, newtup);
+			heap_freetuple(newtup);
+		}
+
+		ReleaseSysCache(oldtup);
+	}
+
+	table_close(pg_subscription_conflict, RowExclusiveLock);
+}
+
+/*
+ * Reset the conflict resolver for this conflict type to its default setting.
+ */
+void
+ResetSubConflictResolver(Oid subid, char *conflict_type)
+{
+	ConflictType idx;
+	ConflictTypeResolver *conflictResolver = NULL;
+	List	   *conflictresolver_list = NIL;
+
+	/* Validate the conflict type and get the index */
+	idx = ValidateConflictType(conflict_type);
+	conflictResolver = palloc(sizeof(ConflictTypeResolver));
+	conflictResolver->conflict_type_name = conflict_type;
+
+	/* Get the default resolver for this conflict_type */
+	conflictResolver->conflict_resolver_name =
+		ConflictResolverNames[ConflictTypeResolverMap[idx][0]];
+
+	/* Create a list of conflict resolvers and update in catalog */
+	conflictresolver_list = lappend(conflictresolver_list, conflictResolver);
+	UpdateSubConflictResolvers(conflictresolver_list, subid);
+
+}
+
+/*
+ * Set Conflict Resolvers on the subscription
+ */
+void
+SetSubConflictResolvers(Oid subId, List *conflict_resolvers)
+{
+	Relation	pg_subscription_conflict;
+	Datum		values[Natts_pg_subscription_conflict];
+	bool		nulls[Natts_pg_subscription_conflict];
+	HeapTuple	newtup = NULL;
+	Oid			conflict_oid;
+
+	pg_subscription_conflict = table_open(SubscriptionConflictId, RowExclusiveLock);
+
+	/* Prepare to update a tuple */
+	memset(nulls, false, sizeof(nulls));
+
+	/* Iterate over the list of resolvers */
+	foreach_ptr(ConflictTypeResolver, conftyperesolver, conflict_resolvers)
+	{
+		values[Anum_pg_subscription_conflict_confsubid - 1] =
+			ObjectIdGetDatum(subId);
+		values[Anum_pg_subscription_conflict_conftype - 1] =
+			CStringGetTextDatum(conftyperesolver->conflict_type_name);
+		values[Anum_pg_subscription_conflict_confres - 1] =
+			CStringGetTextDatum(conftyperesolver->conflict_resolver_name);
+
+		/* Get a new oid and update the tuple into catalog */
+		conflict_oid = GetNewOidWithIndex(pg_subscription_conflict,
+										  SubscriptionConflictOidIndexId,
+										  Anum_pg_subscription_conflict_oid);
+		values[Anum_pg_subscription_conflict_oid - 1] =
+			ObjectIdGetDatum(conflict_oid);
+		newtup = heap_form_tuple(RelationGetDescr(pg_subscription_conflict),
+								 values, nulls);
+		CatalogTupleInsert(pg_subscription_conflict, newtup);
+		heap_freetuple(newtup);
+	}
+
+	table_close(pg_subscription_conflict, RowExclusiveLock);
+}
+
+/*
+ * Remove the subscription conflict resolvers for the subscription id
+ */
+void
+RemoveSubConflictResolvers(Oid subid)
+{
+	Relation	rel;
+	HeapTuple	tup;
+	TableScanDesc scan;
+	ScanKeyData skey[1];
+
+	rel = table_open(SubscriptionConflictId, RowExclusiveLock);
+
+	/*
+	 * Search using the subid to return all conflict resolvers for this
+	 * subscription.
+	 */
+	ScanKeyInit(&skey[0],
+				Anum_pg_subscription_conflict_confsubid,
+				BTEqualStrategyNumber,
+				F_OIDEQ,
+				ObjectIdGetDatum(subid));
+
+	scan = table_beginscan_catalog(rel, 1, skey);
+
+	/* Iterate through the tuples and delete them */
+	while (HeapTupleIsValid(tup = heap_getnext(scan, ForwardScanDirection)))
+		CatalogTupleDelete(rel, &tup->t_self);
+
+	table_endscan(scan);
+	table_close(rel, RowExclusiveLock);
+}
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 1b47c388ce..572eb52a5e 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -395,6 +395,8 @@ static void setupDumpWorker(Archive *AH);
 static TableInfo *getRootTableInfo(const TableInfo *tbinfo);
 static bool forcePartitionRootLoad(const TableInfo *tbinfo);
 static void read_dump_filters(const char *filename, DumpOptions *dopt);
+static bool is_default_resolver(const char *confType, const char *confRes);
+static void destroyConcflictResolverList(SimplePtrList *list);
 
 
 int
@@ -4830,7 +4832,9 @@ getSubscriptions(Archive *fout)
 {
 	DumpOptions *dopt = fout->dopt;
 	PQExpBuffer query;
+	PQExpBuffer confQuery;
 	PGresult   *res;
+	PGresult   *confRes;
 	SubscriptionInfo *subinfo;
 	int			i_tableoid;
 	int			i_oid;
@@ -4851,7 +4855,9 @@ getSubscriptions(Archive *fout)
 	int			i_subenabled;
 	int			i_subfailover;
 	int			i,
-				ntups;
+				j,
+				ntups,
+				ntuples;
 
 	if (dopt->no_subscriptions || fout->remoteVersion < 100000)
 		return;
@@ -5012,6 +5018,37 @@ getSubscriptions(Archive *fout)
 		subinfo[i].subfailover =
 			pg_strdup(PQgetvalue(res, i, i_subfailover));
 
+		/* Populate conflict type fields using the new query */
+		confQuery = createPQExpBuffer();
+		appendPQExpBuffer(confQuery,
+						  "SELECT conftype, confres FROM pg_catalog.pg_subscription_conflict "
+						  "WHERE confsubid = %u order by conftype;", subinfo[i].dobj.catId.oid);
+		confRes = ExecuteSqlQuery(fout, confQuery->data, PGRES_TUPLES_OK);
+
+		ntuples = PQntuples(confRes);
+
+		/* Initialize pointers in the list to NULL */
+		subinfo[i].conflict_resolver = (SimplePtrList)
+		{
+			0
+		};
+
+		/* Store conflict types and resolvers from the query result in subinfo */
+		for (j = 0; j < ntuples; j++)
+		{
+			/* Create the ConflictTypeResolver node */
+			ConflictTypeResolver *conftyperesolver = palloc(sizeof(ConflictTypeResolver));
+
+			conftyperesolver->conflict_type = pg_strdup(PQgetvalue(confRes, j, 0));
+			conftyperesolver->resolver = pg_strdup(PQgetvalue(confRes, j, 1));
+
+			/* Append the node to subinfo's list */
+			simple_ptr_list_append(&subinfo[i].conflict_resolver, conftyperesolver);
+		}
+
+		PQclear(confRes);
+		destroyPQExpBuffer(confQuery);
+
 		/* Decide whether we want to dump it */
 		selectDumpableObject(&(subinfo[i].dobj), fout);
 	}
@@ -5257,6 +5294,35 @@ dumpSubscription(Archive *fout, const SubscriptionInfo *subinfo)
 	if (pg_strcasecmp(subinfo->suborigin, LOGICALREP_ORIGIN_ANY) != 0)
 		appendPQExpBuffer(query, ", origin = %s", subinfo->suborigin);
 
+	/* Add conflict resolvers, if any */
+	if (fout->remoteVersion >= 180000)
+	{
+		bool		first_resolver = true;
+		SimplePtrListCell *cell = NULL;
+		ConflictTypeResolver *conftyperesolver = NULL;
+
+		for (cell = subinfo->conflict_resolver.head; cell; cell = cell->next)
+		{
+			conftyperesolver = (ConflictTypeResolver *) cell->ptr;
+
+			if (!is_default_resolver(conftyperesolver->conflict_type,
+									 conftyperesolver->resolver))
+			{
+				if (first_resolver)
+				{
+					appendPQExpBuffer(query, ") CONFLICT RESOLVER (%s = '%s'",
+									  conftyperesolver->conflict_type,
+									  conftyperesolver->resolver);
+					first_resolver = false;
+				}
+				else
+					appendPQExpBuffer(query, ", %s = '%s'",
+									  conftyperesolver->conflict_type,
+									  conftyperesolver->resolver);
+			}
+		}
+	}
+
 	appendPQExpBufferStr(query, ");\n");
 
 	/*
@@ -5315,6 +5381,9 @@ dumpSubscription(Archive *fout, const SubscriptionInfo *subinfo)
 					 NULL, subinfo->rolname,
 					 subinfo->dobj.catId, 0, subinfo->dobj.dumpId);
 
+	/* Clean-up the conflict_resolver list */
+	destroyConcflictResolverList((SimplePtrList *) &subinfo->conflict_resolver);
+
 	destroyPQExpBuffer(publications);
 	free(pubnames);
 
@@ -19051,3 +19120,48 @@ read_dump_filters(const char *filename, DumpOptions *dopt)
 
 	filter_free(&fstate);
 }
+
+/*
+ * is_default_resolver - checks if the given resolver is the default for the
+ * specified conflict type.
+ */
+static bool
+is_default_resolver(const char *confType, const char *confRes)
+{
+	/*
+	 * The default resolvers for each conflict type are taken from the
+	 * predefined mapping ConflictTypeDefaultResolvers[] in conflict.c.
+	 *
+	 * Only modify these defaults if the corresponding values in conflict.c
+	 * are changed.
+	 */
+
+	if (strcmp(confType, "insert_exists") == 0 ||
+		strcmp(confType, "update_exists") == 0)
+		return strcmp(confRes, "error") == 0;
+	else if (strcmp(confType, "update_missing") == 0 ||
+			 strcmp(confType, "delete_missing") == 0)
+		return strcmp(confRes, "skip") == 0;
+	else if (strcmp(confType, "update_origin_differs") == 0 ||
+			 strcmp(confType, "delete_origin_differs") == 0)
+		return strcmp(confRes, "apply_remote") == 0;
+
+	return false;
+}
+
+/*
+ * destroyConflictResolverList - frees up the SimplePtrList containing
+ * cells pointing to struct ConflictTypeResolver nodes.
+ */
+static void
+destroyConcflictResolverList(SimplePtrList *conflictlist)
+{
+	SimplePtrListCell *cell = NULL;
+
+	/* Free the list items */
+	for (cell = conflictlist->head; cell; cell = cell->next)
+		pfree((ConflictTypeResolver *) cell->ptr);
+
+	/* Destroy the pointer list */
+	simple_ptr_list_destroy(conflictlist);
+}
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 9f907ed5ad..bb16e8b535 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -655,6 +655,13 @@ typedef struct _PublicationSchemaInfo
 /*
  * The SubscriptionInfo struct is used to represent subscription.
  */
+
+typedef struct ConflictTypeResolver
+{
+	const char *conflict_type;
+	const char *resolver;
+} ConflictTypeResolver;
+
 typedef struct _SubscriptionInfo
 {
 	DumpableObject dobj;
@@ -673,6 +680,7 @@ typedef struct _SubscriptionInfo
 	char	   *suborigin;
 	char	   *suboriginremotelsn;
 	char	   *subfailover;
+	SimplePtrList conflict_resolver;
 } SubscriptionInfo;
 
 /*
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 6a36c91083..8599f22044 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -6547,7 +6547,7 @@ describeSubscriptions(const char *pattern, bool verbose)
 	printQueryOpt myopt = pset.popt;
 	static const bool translate_columns[] = {false, false, false, false,
 		false, false, false, false, false, false, false, false, false, false,
-	false};
+	false, false, false, false, false, false, false};
 
 	if (pset.sversion < 100000)
 	{
@@ -6627,12 +6627,21 @@ describeSubscriptions(const char *pattern, bool verbose)
 			appendPQExpBuffer(&buf,
 							  ", subskiplsn AS \"%s\"\n",
 							  gettext_noop("Skip LSN"));
+
+		/* Add conflict resolvers information from pg_subscription_conflict */
+		if (pset.sversion >= 180000)
+			appendPQExpBuffer(&buf,
+							  ", (SELECT string_agg(conftype || ' = ' || confres, ', ' \n"
+							  "  ORDER BY conftype) \n"
+							  " FROM pg_catalog.pg_subscription_conflict c \n"
+							  "   WHERE c.confsubid = s.oid) AS \"%s\"\n",
+							  gettext_noop("Conflict Resolvers"));
 	}
 
 	/* Only display subscriptions in current database. */
 	appendPQExpBufferStr(&buf,
-						 "FROM pg_catalog.pg_subscription\n"
-						 "WHERE subdbid = (SELECT oid\n"
+						 "FROM pg_catalog.pg_subscription s\n"
+						 "WHERE s.subdbid = (SELECT oid\n"
 						 "                 FROM pg_catalog.pg_database\n"
 						 "                 WHERE datname = pg_catalog.current_database())");
 
diff --git a/src/include/catalog/Makefile b/src/include/catalog/Makefile
index 167f91a6e3..f2611c1424 100644
--- a/src/include/catalog/Makefile
+++ b/src/include/catalog/Makefile
@@ -81,7 +81,8 @@ CATALOG_HEADERS := \
 	pg_publication_namespace.h \
 	pg_publication_rel.h \
 	pg_subscription.h \
-	pg_subscription_rel.h
+	pg_subscription_rel.h \
+	pg_subscription_conflict.h
 
 GENERATED_HEADERS := $(CATALOG_HEADERS:%.h=%_d.h)
 
diff --git a/src/include/catalog/meson.build b/src/include/catalog/meson.build
index f70d1daba5..959e1d9ded 100644
--- a/src/include/catalog/meson.build
+++ b/src/include/catalog/meson.build
@@ -69,6 +69,7 @@ catalog_headers = [
   'pg_publication_rel.h',
   'pg_subscription.h',
   'pg_subscription_rel.h',
+  'pg_subscription_conflict.h',
 ]
 
 # The .dat files we need can just be listed alphabetically.
diff --git a/src/include/catalog/pg_subscription_conflict.h b/src/include/catalog/pg_subscription_conflict.h
new file mode 100644
index 0000000000..15f02a1d87
--- /dev/null
+++ b/src/include/catalog/pg_subscription_conflict.h
@@ -0,0 +1,55 @@
+/* -------------------------------------------------------------------------
+ *
+ * pg_subscription_conflict.h
+ *    definition of the "subscription conflict resolver" system
+ *    catalog (pg_subscription_conflict)
+ *
+ * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/catalog/pg_subscription_conflict.h
+ *
+ * NOTES
+ *	  The Catalog.pm module reads this file and derives schema
+ *	  information.
+ *
+ * -------------------------------------------------------------------------
+ */
+#ifndef PG_SUBSCRIPTION_CONFLICT_H
+#define PG_SUBSCRIPTION_CONFLICT_H
+
+#include "catalog/genbki.h"
+#include "catalog/pg_subscription_conflict_d.h"
+
+/* ----------------
+ *		pg_subscription_conflict definition.  cpp turns this into
+ *		typedef struct FormData_pg_subscription_conflict
+ * ----------------
+ */
+CATALOG(pg_subscription_conflict,8881,SubscriptionConflictId)
+{
+	Oid			oid;			/* OID of the object itself */
+	Oid			confsubid BKI_LOOKUP(pg_subscription);	/* OID of subscription */
+
+#ifdef CATALOG_VARLEN			/* variable-length fields start here */
+	text		conftype BKI_FORCE_NOT_NULL;	/* conflict type */
+	text		confres BKI_FORCE_NOT_NULL; /* conflict resolver */
+#endif
+} FormData_pg_subscription_conflict;
+
+/* ----------------
+ *		Form_pg_subscription_conflict corresponds to a pointer to a row with
+ *		the format of pg_subscription_conflict relation.
+ * ----------------
+ */
+typedef FormData_pg_subscription_conflict *Form_pg_subscription_conflict;
+
+DECLARE_TOAST(pg_subscription_conflict, 8882, 8883);
+
+DECLARE_UNIQUE_INDEX_PKEY(pg_subscription_conflict_oid_index, 8884, SubscriptionConflictOidIndexId, pg_subscription_conflict, btree(oid oid_ops));
+
+DECLARE_UNIQUE_INDEX(pg_subscription_conflict_confsubid_conftype_index, 8885, SubscriptionConflictSubIdTypeIndexId, pg_subscription_conflict, btree(confsubid oid_ops, conftype text_ops));
+
+MAKE_SYSCACHE(SUBSCRIPTIONCONFLMAP, pg_subscription_conflict_confsubid_conftype_index, 256);
+
+#endif							/* PG_SUBSCRIPTION_CONFLICT_H */
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 1c314cd907..cd934f7475 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -4216,6 +4216,7 @@ typedef struct CreateSubscriptionStmt
 	char	   *conninfo;		/* Connection string to publisher */
 	List	   *publication;	/* One or more publication to subscribe to */
 	List	   *options;		/* List of DefElem nodes */
+	List	   *resolvers;		/* List of conflict resolvers */
 } CreateSubscriptionStmt;
 
 typedef enum AlterSubscriptionType
@@ -4228,6 +4229,9 @@ typedef enum AlterSubscriptionType
 	ALTER_SUBSCRIPTION_REFRESH,
 	ALTER_SUBSCRIPTION_ENABLED,
 	ALTER_SUBSCRIPTION_SKIP,
+	ALTER_SUBSCRIPTION_CONFLICT_RESOLVERS,
+	ALTER_SUBSCRIPTION_CONFLICT_RESOLVER_RESET,
+	ALTER_SUBSCRIPTION_CONFLICT_RESOLVER_RESET_ALL,
 } AlterSubscriptionType;
 
 typedef struct AlterSubscriptionStmt
@@ -4238,6 +4242,9 @@ typedef struct AlterSubscriptionStmt
 	char	   *conninfo;		/* Connection string to publisher */
 	List	   *publication;	/* One or more publication to subscribe to */
 	List	   *options;		/* List of DefElem nodes */
+	List	   *resolvers;		/* List of conflict type names and resolver
+								 * names */
+	char	   *conflict_type_name; /* The conflict type name to be reset */
 } AlterSubscriptionStmt;
 
 typedef struct DropSubscriptionStmt
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 899d64ad55..e62b0c6945 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -376,6 +376,7 @@ PG_KEYWORD("repeatable", REPEATABLE, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("replace", REPLACE, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("replica", REPLICA, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("reset", RESET, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("resolver", RESOLVER, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("restart", RESTART, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("restrict", RESTRICT, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("return", RETURN, UNRESERVED_KEYWORD, BARE_LABEL)
diff --git a/src/include/replication/conflict.h b/src/include/replication/conflict.h
index c759677ff5..c5865a1d58 100644
--- a/src/include/replication/conflict.h
+++ b/src/include/replication/conflict.h
@@ -1,6 +1,6 @@
 /*-------------------------------------------------------------------------
  * conflict.h
- *	   Exports for conflicts logging.
+ *	   Exports for conflicts logging and resolvers configuration.
  *
  * Copyright (c) 2024, PostgreSQL Global Development Group
  *
@@ -10,6 +10,7 @@
 #define CONFLICT_H
 
 #include "nodes/execnodes.h"
+#include "parser/parse_node.h"
 #include "utils/timestamp.h"
 
 /*
@@ -50,6 +51,41 @@ typedef enum
 
 #define CONFLICT_NUM_TYPES (CT_DELETE_MISSING + 1)
 
+/*
+ * Conflict resolvers that can be used to resolve various conflicts.
+ *
+ * See ConflictTypeResolverMap in conflict.c to find out which all
+ * resolvers are supported for each conflict type.
+ */
+typedef enum ConflictResolver
+{
+	/* Apply the remote change */
+	CR_APPLY_REMOTE,
+
+	/* Keep the local change */
+	CR_KEEP_LOCAL,
+
+	/* Apply the remote change; skip if it cannot be applied */
+	CR_APPLY_OR_SKIP,
+
+	/* Apply the remote change; emit error if it cannot be applied */
+	CR_APPLY_OR_ERROR,
+
+	/* Skip applying the change */
+	CR_SKIP,
+
+	/* Error out */
+	CR_ERROR,
+} ConflictResolver;
+
+#define CONFLICT_NUM_RESOLVERS (CR_ERROR + 1)
+
+typedef struct ConflictTypeResolver
+{
+	const char *conflict_type_name;
+	const char *conflict_resolver_name;
+} ConflictTypeResolver;
+
 extern bool GetTupleTransactionInfo(TupleTableSlot *localslot,
 									TransactionId *xmin,
 									RepOriginId *localorigin,
@@ -62,5 +98,16 @@ extern void ReportApplyConflict(EState *estate, ResultRelInfo *relinfo,
 								Oid indexoid, TransactionId localxmin,
 								RepOriginId localorigin, TimestampTz localts);
 extern void InitConflictIndexes(ResultRelInfo *relInfo);
+extern void SetSubConflictResolvers(Oid subId, List *resolvers);
+extern void RemoveSubConflictResolvers(Oid confid);
+extern List *ParseAndGetSubConflictResolvers(ParseState *pstate,
+											 List *stmtresolvers,
+											 bool add_defaults);
+extern void UpdateSubConflictResolvers(List *conflict_resolvers, Oid subid);
+extern ConflictType ValidateConflictType(const char *conflict_type);
+extern ConflictType ValidateConflictTypeAndResolver(const char *conflict_type,
+													const char *conflict_resolver);
+extern List *GetDefaultConflictResolvers(void);
+extern void ResetSubConflictResolver(Oid subid, char *conflict_type);
 
 #endif
diff --git a/src/test/regress/expected/oidjoins.out b/src/test/regress/expected/oidjoins.out
index 215eb899be..42bf2cce92 100644
--- a/src/test/regress/expected/oidjoins.out
+++ b/src/test/regress/expected/oidjoins.out
@@ -266,3 +266,4 @@ NOTICE:  checking pg_subscription {subdbid} => pg_database {oid}
 NOTICE:  checking pg_subscription {subowner} => pg_authid {oid}
 NOTICE:  checking pg_subscription_rel {srsubid} => pg_subscription {oid}
 NOTICE:  checking pg_subscription_rel {srrelid} => pg_class {oid}
+NOTICE:  checking pg_subscription_conflict {confsubid} => pg_subscription {oid}
diff --git a/src/test/regress/expected/subscription.out b/src/test/regress/expected/subscription.out
index 17d48b1685..97911415a4 100644
--- a/src/test/regress/expected/subscription.out
+++ b/src/test/regress/expected/subscription.out
@@ -116,18 +116,18 @@ CREATE SUBSCRIPTION regress_testsub4 CONNECTION 'dbname=regress_doesnotexist' PU
 WARNING:  subscription was created, but is not connected
 HINT:  To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
 \dRs+ regress_testsub4
-                                                                                                                 List of subscriptions
-       Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
-------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
- regress_testsub4 | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | none   | t                 | f             | f        | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                                                                                                     List of subscriptions
+       Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN |                                                                           Conflict Resolvers                                                                           
+------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ regress_testsub4 | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | none   | t                 | f             | f        | off                | dbname=regress_doesnotexist | 0/0      | delete_missing = skip, delete_origin_differs = apply_remote, insert_exists = error, update_exists = error, update_missing = skip, update_origin_differs = apply_remote
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub4 SET (origin = any);
 \dRs+ regress_testsub4
-                                                                                                                 List of subscriptions
-       Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
-------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
- regress_testsub4 | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | f        | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                                                                                                     List of subscriptions
+       Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN |                                                                           Conflict Resolvers                                                                           
+------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ regress_testsub4 | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | f        | off                | dbname=regress_doesnotexist | 0/0      | delete_missing = skip, delete_origin_differs = apply_remote, insert_exists = error, update_exists = error, update_missing = skip, update_origin_differs = apply_remote
 (1 row)
 
 DROP SUBSCRIPTION regress_testsub3;
@@ -145,10 +145,10 @@ ALTER SUBSCRIPTION regress_testsub CONNECTION 'foobar';
 ERROR:  invalid connection string syntax: missing "=" after "foobar" in connection info string
 
 \dRs+
-                                                                                                                List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | f        | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                                                                                                     List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN |                                                                           Conflict Resolvers                                                                           
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | f        | off                | dbname=regress_doesnotexist | 0/0      | delete_missing = skip, delete_origin_differs = apply_remote, insert_exists = error, update_exists = error, update_missing = skip, update_origin_differs = apply_remote
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET PUBLICATION testpub2, testpub3 WITH (refresh = false);
@@ -157,10 +157,10 @@ ALTER SUBSCRIPTION regress_testsub SET (slot_name = 'newname');
 ALTER SUBSCRIPTION regress_testsub SET (password_required = false);
 ALTER SUBSCRIPTION regress_testsub SET (run_as_owner = true);
 \dRs+
-                                                                                                                     List of subscriptions
-      Name       |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |           Conninfo           | Skip LSN 
------------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+------------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | f                 | t             | f        | off                | dbname=regress_doesnotexist2 | 0/0
+                                                                                                                                                                                                         List of subscriptions
+      Name       |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |           Conninfo           | Skip LSN |                                                                           Conflict Resolvers                                                                           
+-----------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+------------------------------+----------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ regress_testsub | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | f                 | t             | f        | off                | dbname=regress_doesnotexist2 | 0/0      | delete_missing = skip, delete_origin_differs = apply_remote, insert_exists = error, update_exists = error, update_missing = skip, update_origin_differs = apply_remote
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (password_required = true);
@@ -176,10 +176,10 @@ ERROR:  unrecognized subscription parameter: "create_slot"
 -- ok
 ALTER SUBSCRIPTION regress_testsub SKIP (lsn = '0/12345');
 \dRs+
-                                                                                                                     List of subscriptions
-      Name       |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |           Conninfo           | Skip LSN 
------------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+------------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | t                 | f             | f        | off                | dbname=regress_doesnotexist2 | 0/12345
+                                                                                                                                                                                                         List of subscriptions
+      Name       |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |           Conninfo           | Skip LSN |                                                                           Conflict Resolvers                                                                           
+-----------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+------------------------------+----------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ regress_testsub | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | t                 | f             | f        | off                | dbname=regress_doesnotexist2 | 0/12345  | delete_missing = skip, delete_origin_differs = apply_remote, insert_exists = error, update_exists = error, update_missing = skip, update_origin_differs = apply_remote
 (1 row)
 
 -- ok - with lsn = NONE
@@ -188,10 +188,10 @@ ALTER SUBSCRIPTION regress_testsub SKIP (lsn = NONE);
 ALTER SUBSCRIPTION regress_testsub SKIP (lsn = '0/0');
 ERROR:  invalid WAL location (LSN): 0/0
 \dRs+
-                                                                                                                     List of subscriptions
-      Name       |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |           Conninfo           | Skip LSN 
------------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+------------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | t                 | f             | f        | off                | dbname=regress_doesnotexist2 | 0/0
+                                                                                                                                                                                                         List of subscriptions
+      Name       |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |           Conninfo           | Skip LSN |                                                                           Conflict Resolvers                                                                           
+-----------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+------------------------------+----------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ regress_testsub | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | t                 | f             | f        | off                | dbname=regress_doesnotexist2 | 0/0      | delete_missing = skip, delete_origin_differs = apply_remote, insert_exists = error, update_exists = error, update_missing = skip, update_origin_differs = apply_remote
 (1 row)
 
 BEGIN;
@@ -223,10 +223,10 @@ ALTER SUBSCRIPTION regress_testsub_foo SET (synchronous_commit = foobar);
 ERROR:  invalid value for parameter "synchronous_commit": "foobar"
 HINT:  Available values: local, remote_write, remote_apply, on, off.
 \dRs+
-                                                                                                                       List of subscriptions
-        Name         |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |           Conninfo           | Skip LSN 
----------------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+------------------------------+----------
- regress_testsub_foo | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | t                 | f             | f        | local              | dbname=regress_doesnotexist2 | 0/0
+                                                                                                                                                                                                           List of subscriptions
+        Name         |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |           Conninfo           | Skip LSN |                                                                           Conflict Resolvers                                                                           
+---------------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+------------------------------+----------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ regress_testsub_foo | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | t                 | f             | f        | local              | dbname=regress_doesnotexist2 | 0/0      | delete_missing = skip, delete_origin_differs = apply_remote, insert_exists = error, update_exists = error, update_missing = skip, update_origin_differs = apply_remote
 (1 row)
 
 -- rename back to keep the rest simple
@@ -255,19 +255,19 @@ CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUB
 WARNING:  subscription was created, but is not connected
 HINT:  To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
 \dRs+
-                                                                                                                List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | t      | off       | d                | f                | any    | t                 | f             | f        | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                                                                                                     List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN |                                                                           Conflict Resolvers                                                                           
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | t      | off       | d                | f                | any    | t                 | f             | f        | off                | dbname=regress_doesnotexist | 0/0      | delete_missing = skip, delete_origin_differs = apply_remote, insert_exists = error, update_exists = error, update_missing = skip, update_origin_differs = apply_remote
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (binary = false);
 ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
 \dRs+
-                                                                                                                List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | f        | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                                                                                                     List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN |                                                                           Conflict Resolvers                                                                           
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | f        | off                | dbname=regress_doesnotexist | 0/0      | delete_missing = skip, delete_origin_differs = apply_remote, insert_exists = error, update_exists = error, update_missing = skip, update_origin_differs = apply_remote
 (1 row)
 
 DROP SUBSCRIPTION regress_testsub;
@@ -279,27 +279,27 @@ CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUB
 WARNING:  subscription was created, but is not connected
 HINT:  To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
 \dRs+
-                                                                                                                List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | on        | d                | f                | any    | t                 | f             | f        | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                                                                                                     List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN |                                                                           Conflict Resolvers                                                                           
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | on        | d                | f                | any    | t                 | f             | f        | off                | dbname=regress_doesnotexist | 0/0      | delete_missing = skip, delete_origin_differs = apply_remote, insert_exists = error, update_exists = error, update_missing = skip, update_origin_differs = apply_remote
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (streaming = parallel);
 \dRs+
-                                                                                                                List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | parallel  | d                | f                | any    | t                 | f             | f        | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                                                                                                     List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN |                                                                           Conflict Resolvers                                                                           
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | parallel  | d                | f                | any    | t                 | f             | f        | off                | dbname=regress_doesnotexist | 0/0      | delete_missing = skip, delete_origin_differs = apply_remote, insert_exists = error, update_exists = error, update_missing = skip, update_origin_differs = apply_remote
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (streaming = false);
 ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
 \dRs+
-                                                                                                                List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | f        | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                                                                                                     List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN |                                                                           Conflict Resolvers                                                                           
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | f        | off                | dbname=regress_doesnotexist | 0/0      | delete_missing = skip, delete_origin_differs = apply_remote, insert_exists = error, update_exists = error, update_missing = skip, update_origin_differs = apply_remote
 (1 row)
 
 -- fail - publication already exists
@@ -314,10 +314,10 @@ ALTER SUBSCRIPTION regress_testsub ADD PUBLICATION testpub1, testpub2 WITH (refr
 ALTER SUBSCRIPTION regress_testsub ADD PUBLICATION testpub1, testpub2 WITH (refresh = false);
 ERROR:  publication "testpub1" is already in subscription "regress_testsub"
 \dRs+
-                                                                                                                        List of subscriptions
-      Name       |           Owner           | Enabled |         Publication         | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-----------------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub,testpub1,testpub2} | f      | off       | d                | f                | any    | t                 | f             | f        | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                                                                                                             List of subscriptions
+      Name       |           Owner           | Enabled |         Publication         | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN |                                                                           Conflict Resolvers                                                                           
+-----------------+---------------------------+---------+-----------------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ regress_testsub | regress_subscription_user | f       | {testpub,testpub1,testpub2} | f      | off       | d                | f                | any    | t                 | f             | f        | off                | dbname=regress_doesnotexist | 0/0      | delete_missing = skip, delete_origin_differs = apply_remote, insert_exists = error, update_exists = error, update_missing = skip, update_origin_differs = apply_remote
 (1 row)
 
 -- fail - publication used more than once
@@ -332,10 +332,10 @@ ERROR:  publication "testpub3" is not in subscription "regress_testsub"
 -- ok - delete publications
 ALTER SUBSCRIPTION regress_testsub DROP PUBLICATION testpub1, testpub2 WITH (refresh = false);
 \dRs+
-                                                                                                                List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | f        | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                                                                                                     List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN |                                                                           Conflict Resolvers                                                                           
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | f        | off                | dbname=regress_doesnotexist | 0/0      | delete_missing = skip, delete_origin_differs = apply_remote, insert_exists = error, update_exists = error, update_missing = skip, update_origin_differs = apply_remote
 (1 row)
 
 DROP SUBSCRIPTION regress_testsub;
@@ -371,19 +371,19 @@ CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUB
 WARNING:  subscription was created, but is not connected
 HINT:  To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
 \dRs+
-                                                                                                                List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | p                | f                | any    | t                 | f             | f        | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                                                                                                     List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN |                                                                           Conflict Resolvers                                                                           
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | p                | f                | any    | t                 | f             | f        | off                | dbname=regress_doesnotexist | 0/0      | delete_missing = skip, delete_origin_differs = apply_remote, insert_exists = error, update_exists = error, update_missing = skip, update_origin_differs = apply_remote
 (1 row)
 
 -- we can alter streaming when two_phase enabled
 ALTER SUBSCRIPTION regress_testsub SET (streaming = true);
 \dRs+
-                                                                                                                List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | on        | p                | f                | any    | t                 | f             | f        | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                                                                                                     List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN |                                                                           Conflict Resolvers                                                                           
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | on        | p                | f                | any    | t                 | f             | f        | off                | dbname=regress_doesnotexist | 0/0      | delete_missing = skip, delete_origin_differs = apply_remote, insert_exists = error, update_exists = error, update_missing = skip, update_origin_differs = apply_remote
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
@@ -393,10 +393,109 @@ CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUB
 WARNING:  subscription was created, but is not connected
 HINT:  To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
 \dRs+
-                                                                                                                List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | on        | p                | f                | any    | t                 | f             | f        | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                                                                                                     List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN |                                                                           Conflict Resolvers                                                                           
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | on        | p                | f                | any    | t                 | f             | f        | off                | dbname=regress_doesnotexist | 0/0      | delete_missing = skip, delete_origin_differs = apply_remote, insert_exists = error, update_exists = error, update_missing = skip, update_origin_differs = apply_remote
+(1 row)
+
+ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
+DROP SUBSCRIPTION regress_testsub;
+-- fail - invalid conflict type
+CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUBLICATION testpub CONFLICT RESOLVER (foo = 'keep_local') WITH (connect = false);
+ERROR:  foo is not a valid conflict type
+-- fail - invalid conflict resolver
+CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUBLICATION testpub CONFLICT RESOLVER (insert_exists = foo) WITH (connect = false);
+ERROR:  foo is not a valid conflict resolver
+-- fail - invalid resolver for that conflict type
+CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUBLICATION testpub
+ CONFLICT RESOLVER (update_missing = 'apply_remote') WITH (connect = false);
+ERROR:  apply_remote is not a valid conflict resolver for conflict type update_missing
+-- fail - duplicate conflict type
+CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUBLICATION testpub CONFLICT RESOLVER (insert_exists = 'keep_local', insert_exists = 'keep_local');
+ERROR:  conflicting or redundant options
+LINE 1: ... CONFLICT RESOLVER (insert_exists = 'keep_local', insert_exi...
+                                                             ^
+-- creating subscription with no explicit conflict resolvers should
+-- configure default conflict resolvers
+CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUBLICATION testpub WITH (connect = false);
+WARNING:  subscription was created, but is not connected
+HINT:  To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
+\dRs+
+                                                                                                                                                                                                     List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN |                                                                           Conflict Resolvers                                                                           
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | f        | off                | dbname=regress_doesnotexist | 0/0      | delete_missing = skip, delete_origin_differs = apply_remote, insert_exists = error, update_exists = error, update_missing = skip, update_origin_differs = apply_remote
+(1 row)
+
+ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
+DROP SUBSCRIPTION regress_testsub;
+-- ok - valid conflict types and resolvers
+CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUBLICATION testpub CONFLICT RESOLVER (insert_exists = 'keep_local', update_missing = 'skip', delete_origin_differs = 'keep_local' ) WITH (connect = false);
+WARNING:  conflict detection and resolution could be incomplete due to disabled track_commit_timestamp
+DETAIL:  Conflicts update_origin_differs and delete_origin_differs cannot be detected, and the origin and commit timestamp for the local row will not be logged.
+WARNING:  subscription was created, but is not connected
+HINT:  To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
+--check if above are configured; for non specified conflict types, default resolvers should be seen
+\dRs+
+                                                                                                                                                                                                      List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN |                                                                            Conflict Resolvers                                                                             
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | f        | off                | dbname=regress_doesnotexist | 0/0      | delete_missing = skip, delete_origin_differs = keep_local, insert_exists = keep_local, update_exists = error, update_missing = skip, update_origin_differs = apply_remote
+(1 row)
+
+-- fail - altering with invalid conflict type
+ALTER SUBSCRIPTION regress_testsub CONFLICT RESOLVER (foo = 'keep_local');
+ERROR:  foo is not a valid conflict type
+-- fail - altering with invalid conflict resolver
+ALTER SUBSCRIPTION regress_testsub CONFLICT RESOLVER (insert_exists = 'foo');
+ERROR:  foo is not a valid conflict resolver
+-- fail - altering with duplicate conflict type
+ALTER SUBSCRIPTION regress_testsub CONFLICT RESOLVER (insert_exists = 'apply_remote', insert_exists = 'apply_remote');
+ERROR:  conflicting or redundant options
+LINE 1: ...ONFLICT RESOLVER (insert_exists = 'apply_remote', insert_exi...
+                                                             ^
+-- ok - valid conflict types and resolvers
+ALTER SUBSCRIPTION regress_testsub CONFLICT RESOLVER (insert_exists = 'apply_remote', update_missing = 'skip', delete_origin_differs = 'keep_local' );
+WARNING:  conflict detection and resolution could be incomplete due to disabled track_commit_timestamp
+DETAIL:  Conflicts update_origin_differs and delete_origin_differs cannot be detected, and the origin and commit timestamp for the local row will not be logged.
+\dRs+
+                                                                                                                                                                                                       List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN |                                                                             Conflict Resolvers                                                                              
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | f        | off                | dbname=regress_doesnotexist | 0/0      | delete_missing = skip, delete_origin_differs = keep_local, insert_exists = apply_remote, update_exists = error, update_missing = skip, update_origin_differs = apply_remote
+(1 row)
+
+-- ok - valid conflict types and resolvers
+ALTER SUBSCRIPTION regress_testsub CONFLICT RESOLVER (update_exists = 'keep_local', delete_missing = 'error', update_origin_differs = 'error');
+WARNING:  conflict detection and resolution could be incomplete due to disabled track_commit_timestamp
+DETAIL:  Conflicts update_origin_differs and delete_origin_differs cannot be detected, and the origin and commit timestamp for the local row will not be logged.
+\dRs+
+                                                                                                                                                                                                       List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN |                                                                             Conflict Resolvers                                                                             
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | f        | off                | dbname=regress_doesnotexist | 0/0      | delete_missing = error, delete_origin_differs = keep_local, insert_exists = apply_remote, update_exists = keep_local, update_missing = skip, update_origin_differs = error
+(1 row)
+
+-- fail - reset with an invalid conflit type
+ALTER SUBSCRIPTION regress_testsub RESET CONFLICT RESOLVER for 'foo';
+ERROR:  foo is not a valid conflict type
+-- ok - valid conflict type
+ALTER SUBSCRIPTION regress_testsub RESET CONFLICT RESOLVER for 'insert_exists';
+\dRs+
+                                                                                                                                                                                                   List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN |                                                                         Conflict Resolvers                                                                          
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | f        | off                | dbname=regress_doesnotexist | 0/0      | delete_missing = error, delete_origin_differs = keep_local, insert_exists = error, update_exists = keep_local, update_missing = skip, update_origin_differs = error
+(1 row)
+
+-- ok - reset ALL
+ALTER SUBSCRIPTION regress_testsub RESET CONFLICT RESOLVER ALL;
+\dRs+
+                                                                                                                                                                                                     List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN |                                                                           Conflict Resolvers                                                                           
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | f        | off                | dbname=regress_doesnotexist | 0/0      | delete_missing = skip, delete_origin_differs = apply_remote, insert_exists = error, update_exists = error, update_missing = skip, update_origin_differs = apply_remote
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
@@ -409,18 +508,18 @@ CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUB
 WARNING:  subscription was created, but is not connected
 HINT:  To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
 \dRs+
-                                                                                                                List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | f        | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                                                                                                     List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN |                                                                           Conflict Resolvers                                                                           
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | f        | off                | dbname=regress_doesnotexist | 0/0      | delete_missing = skip, delete_origin_differs = apply_remote, insert_exists = error, update_exists = error, update_missing = skip, update_origin_differs = apply_remote
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (disable_on_error = true);
 \dRs+
-                                                                                                                List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | t                | any    | t                 | f             | f        | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                                                                                                     List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN |                                                                           Conflict Resolvers                                                                           
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | t                | any    | t                 | f             | f        | off                | dbname=regress_doesnotexist | 0/0      | delete_missing = skip, delete_origin_differs = apply_remote, insert_exists = error, update_exists = error, update_missing = skip, update_origin_differs = apply_remote
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
diff --git a/src/test/regress/sql/subscription.sql b/src/test/regress/sql/subscription.sql
index 007c9e7037..3f08869bce 100644
--- a/src/test/regress/sql/subscription.sql
+++ b/src/test/regress/sql/subscription.sql
@@ -272,6 +272,69 @@ CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUB
 ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
 DROP SUBSCRIPTION regress_testsub;
 
+-- fail - invalid conflict type
+CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUBLICATION testpub CONFLICT RESOLVER (foo = 'keep_local') WITH (connect = false);
+
+-- fail - invalid conflict resolver
+CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUBLICATION testpub CONFLICT RESOLVER (insert_exists = foo) WITH (connect = false);
+
+-- fail - invalid resolver for that conflict type
+CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUBLICATION testpub
+ CONFLICT RESOLVER (update_missing = 'apply_remote') WITH (connect = false);
+
+-- fail - duplicate conflict type
+CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUBLICATION testpub CONFLICT RESOLVER (insert_exists = 'keep_local', insert_exists = 'keep_local');
+
+-- creating subscription with no explicit conflict resolvers should
+-- configure default conflict resolvers
+CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUBLICATION testpub WITH (connect = false);
+
+\dRs+
+
+ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
+DROP SUBSCRIPTION regress_testsub;
+
+-- ok - valid conflict types and resolvers
+CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUBLICATION testpub CONFLICT RESOLVER (insert_exists = 'keep_local', update_missing = 'skip', delete_origin_differs = 'keep_local' ) WITH (connect = false);
+
+--check if above are configured; for non specified conflict types, default resolvers should be seen
+\dRs+
+
+-- fail - altering with invalid conflict type
+ALTER SUBSCRIPTION regress_testsub CONFLICT RESOLVER (foo = 'keep_local');
+
+-- fail - altering with invalid conflict resolver
+ALTER SUBSCRIPTION regress_testsub CONFLICT RESOLVER (insert_exists = 'foo');
+
+-- fail - altering with duplicate conflict type
+ALTER SUBSCRIPTION regress_testsub CONFLICT RESOLVER (insert_exists = 'apply_remote', insert_exists = 'apply_remote');
+
+-- ok - valid conflict types and resolvers
+ALTER SUBSCRIPTION regress_testsub CONFLICT RESOLVER (insert_exists = 'apply_remote', update_missing = 'skip', delete_origin_differs = 'keep_local' );
+
+\dRs+
+
+-- ok - valid conflict types and resolvers
+ALTER SUBSCRIPTION regress_testsub CONFLICT RESOLVER (update_exists = 'keep_local', delete_missing = 'error', update_origin_differs = 'error');
+
+\dRs+
+
+-- fail - reset with an invalid conflit type
+ALTER SUBSCRIPTION regress_testsub RESET CONFLICT RESOLVER for 'foo';
+
+-- ok - valid conflict type
+ALTER SUBSCRIPTION regress_testsub RESET CONFLICT RESOLVER for 'insert_exists';
+
+\dRs+
+
+-- ok - reset ALL
+ALTER SUBSCRIPTION regress_testsub RESET CONFLICT RESOLVER ALL;
+
+\dRs+
+
+ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
+DROP SUBSCRIPTION regress_testsub;
+
 -- fail - disable_on_error must be boolean
 CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUBLICATION testpub WITH (connect = false, disable_on_error = foo);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index a65e1c07c5..bf1ea95f18 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -468,7 +468,9 @@ ConditionVariableMinimallyPadded
 ConditionalStack
 ConfigData
 ConfigVariable
+ConflictResolver
 ConflictType
+ConflictTypeResolver
 ConnCacheEntry
 ConnCacheKey
 ConnParams
@@ -863,6 +865,7 @@ FormData_pg_statistic
 FormData_pg_statistic_ext
 FormData_pg_statistic_ext_data
 FormData_pg_subscription
+FormData_pg_subscription_conflict
 FormData_pg_subscription_rel
 FormData_pg_tablespace
 FormData_pg_transform
-- 
2.34.1

