Hi, here are my code/test review comments for patch v14-0001.

(the v14-0001 docs review was already previously posted)

======
src/backend/commands/subscriptioncmds.c

parse_subscription_conflict_resolvers:

1.
+ /* Check if the conflict type already exists in the list */
+ if (list_member(SeenTypes, makeString(defel->defname)))

This 'SeenTypes' logic of string comparison of list elements all seems
overkill to me.

There is a known number of conflict types, so why not use a more
efficient bool array:
e.g. bool seen_conflict_type[CONFLICT_NUM_TYPES] = {0};

NOTE - I've already made this change in the nits attachment to
demonstrate that it works fine.

~

2.
+ foreach(lc, stmtresolvers)
+ {
+ DefElem    *defel = (DefElem *) lfirst(lc);

There are macros to do this, so you don't need to use lfirst(). e.g.
foreach_ptr?

~

3.
nit (a) - remove excessive blank lines
nit (b) - minor comment reword + periods.

~~~

CreateSubscription:

4.
  bits32 supported_opts;
  SubOpts opts = {0};
  AclResult aclresult;
+ ConflictTypeResolver conflictResolvers[CONFLICT_NUM_TYPES];

nit - Suggest rename to 'conflict_resolvers' to keep similar style to
other variables.

~

5.
nit - add a missing period to some comments.

~~~

AlterSubscription:

6.
nit (a) - remove excessing blank lines.
nit (b) - add missing period in comments.
nit (c) - rename 'conflictResolvers' to 'conflict_resolvers' for
consistent variable style

======
src/backend/parser/gram.y

7.
+ | ALTER SUBSCRIPTION name RESET CONFLICT RESOLVER FOR conflict_type
+ {
+ AlterSubscriptionStmt *n =
+ makeNode(AlterSubscriptionStmt);
+
+ n->kind = ALTER_SUBSCRIPTION_RESET_CONFLICT_RESOLVER;
+ n->subname = $3;
+ n->conflict_type = $8;
+ $$ = (Node *) n;
+ }

Why does this only support resetting resolvers for one conflict type at a time?

~

8.
nit - add whitespace blank line

======
src/backend/replication/logical/conflict.c

9.
Add some static assertions for all of the arrays to ensure everything
is declared as expected.

(I've made this change in the nit attachment)

~~~

10.
+#define CONFLICT_TYPE_MAX_RESOLVERS 4

It's a bit fiddly introducing this constant just for the map
dimensions. You might as well use the already defined
CONFLICT_NUM_RESOLVERS.

Yes, I know they are not exactly the same. But, the extra few ints of
space wasted reusing this is insignificant, and there is no
performance loss in the lookup logic (after my other review comment
later). IMO it is easier to use what you already have instead of
introducing yet another hard-to-explain constant.

(I've made this change in the nit attachment)

~~~

11.
+static const int ConflictTypeResolverMap[][CONFLICT_TYPE_MAX_RESOLVERS] = {
+ [CT_INSERT_EXISTS] = {CR_APPLY_REMOTE, CR_KEEP_LOCAL, CR_ERROR},
+ [CT_UPDATE_EXISTS] = {CR_APPLY_REMOTE, CR_KEEP_LOCAL, CR_ERROR},
+ [CT_UPDATE_ORIGIN_DIFFERS] = {CR_APPLY_REMOTE, CR_KEEP_LOCAL, CR_ERROR},
+ [CT_UPDATE_MISSING] = {CR_APPLY_OR_SKIP, CR_APPLY_OR_ERROR, CR_SKIP,
CR_ERROR},
+ [CT_DELETE_MISSING] = {CR_SKIP, CR_ERROR},
+ [CT_DELETE_ORIGIN_DIFFERS] = {CR_APPLY_REMOTE, CR_KEEP_LOCAL, CR_ERROR}
+};

FYI - There is a subtle quirk (and potential bug) lurking in the way
this map is declared. The array elements for resolvers that are not
defined will default to value 0. So "{CR_SKIP, CR_ERROR}" is
essentially saying "{CR_SKIP, CR_ERROR, 0, 0}", and that means that
the enums for resolvers cannot have a value 0 because then this
ConflictTypeResolverMap would be broken. I see that this was already
handled (enums started at 1) but there was no explanatory comment so
it would be easy to unknowingly break it.

A better way (what I am suggesting here) would be to have a -1 marker
to indicate the end of the list of valid resolvers. This removes any
doubt, and it means the enums can start from 0 as normal, and it means
you can quick-exit from the lookup code (suggested later below) for a
performance gain. Basically, win-win-win.

For example:
 * NOTE: -1 is an end marker for the list of valid resolvers for each conflict
 * type.
 */
static const int ConflictTypeResolverMap[][CONFLICT_NUM_RESOLVERS+1] = {
  [CT_INSERT_EXISTS] = {CR_APPLY_REMOTE, CR_KEEP_LOCAL, CR_ERROR, -1},
  [CT_UPDATE_EXISTS] = {CR_APPLY_REMOTE, CR_KEEP_LOCAL, CR_ERROR, -1},
  [CT_UPDATE_ORIGIN_DIFFERS] = {CR_APPLY_REMOTE, CR_KEEP_LOCAL, CR_ERROR, -1},
  [CT_UPDATE_MISSING] = {CR_APPLY_OR_SKIP, CR_APPLY_OR_ERROR, CR_SKIP,
CR_ERROR, -1},
  [CT_DELETE_MISSING] = {CR_SKIP, CR_ERROR, -1},
  [CT_DELETE_ORIGIN_DIFFERS] = {CR_APPLY_REMOTE, CR_KEEP_LOCAL, CR_ERROR, -1}
};

(I have made this change already in the nit attachment)

~~~

12.
+/*
+ * Default conflict resolver for each conflict type.
+ */
+static const int ConflictTypeDefaultResolvers[] = {
+ [CT_INSERT_EXISTS] = CR_ERROR,
+ [CT_UPDATE_EXISTS] = CR_ERROR,
+ [CT_UPDATE_ORIGIN_DIFFERS] = CR_APPLY_REMOTE,
+ [CT_UPDATE_MISSING] = CR_SKIP,
+ [CT_DELETE_MISSING] = CR_SKIP,
+ [CT_DELETE_ORIGIN_DIFFERS] = CR_APPLY_REMOTE
+
+};
+

IMO you can throw all this away. Instead, simply rearrange the
resolvers in ConflictTypeResolverMap[] and provide a comment to say
that the resolver at index [0] of the valid resolver lists is the
"default" resolver for each conflict type.

(I have made this change already in the nit attachment)

~~~

SetDefaultResolvers:

13.
+/*
+ * Set default values for CONFLICT RESOLVERS for each conflict type
+ */
+void
+SetDefaultResolvers(ConflictTypeResolver * conflictResolvers)
+{
+ ConflictType type;
+
+ for (type = CT_MIN; type <= CT_MAX; type++)
+ {
+ conflictResolvers[type].conflict_type = ConflictTypeNames[type];
+ conflictResolvers[type].resolver =
+ ConflictResolverNames[ConflictTypeDefaultResolvers[type]];
+ }
+}

nit (a) - remove extra blank line about this function
nit (b) - add a period to the function comment
nit (c) - tidy the param.
nit (d) - use for-loop variable
nit (e) - don't need CT_MIN and CT_MAX

~~~

validate_conflict_type_and_resolver:

14.
AFAIK, you could also break out of the resolver loop early if you hit
an invalid resolver, because that means you have already fallen off
the end of the valid resolvers.

e.g. do this (in conjunction with the other suggested
ConflictTypeResolverMap changes)

if (candidate < 0)
{
/* No more possible resolvers for this conflict type. */
break;
}

~

15.
nit (a) - use for-loop variable
nit (b) - improve the comments a bit
nit (c) - don't need CT_MIN and CT_MAX
nit (d) - don't need CR_MIN and CR_MAX
nit (e) - use loop variable 'i'
nit (f) - add a blank line before the return
nit (g) - remove excessive blank lines in the function (after the return)

~~~

GetAndValidateSubsConflictResolverList:

16.
nit (a) - minor tweak function comment
nit (b) - put the "ConflictTypeResolver *conftyperesolver = NULL" in
lower scope.
nit (c) - tweak comments for periods etc.

~

17.
+ /* Add the conflict type to the list of seen types */
+ conflictTypes = lappend(conflictTypes,
+ makeString((char *) conftyperesolver->conflict_type));

All this messing with lists of strings seems somewhat inefficient. We
already know the enum by this point (e.g.
validate_conflict_type_and_resolver returns it) so the "seen" logic
should be possible with a simple bool array.

(I've made this change in the nits attachment to give an example of
what I mean. It seems to work just fine).

~~~

UpdateSubConflictResolvers:

18.
+ if (HeapTupleIsValid(oldtup))
+ {
+ /* Update the new resolver */
+ values[Anum_pg_subscription_conflict_confrres - 1] =
+ CStringGetTextDatum(conftyperesolver->resolver);
+ replaces[Anum_pg_subscription_conflict_confrres - 1] = true;
+
+ newtup = heap_modify_tuple(oldtup, RelationGetDescr(pg_subscription_conflict),
+    values, nulls, replaces);
+ CatalogTupleUpdate(pg_subscription_conflict, &oldtup->t_self, newtup);
+ ReleaseSysCache(oldtup);
+ heap_freetuple(newtup);
+ }
+ else
+ elog(ERROR, "cache lookup failed for table conflict %s for subid %u",
+ conftyperesolver->conflict_type, subid);

I think this might be more easily expressed by reversing the condition:

if (!HeapTupleIsValid(oldtup))
    elog(ERROR, "cache lookup failed for table conflict %s for subid %u",
        conftyperesolver->conflict_type, subid);
...

~~~

ResetConflictResolver:

19.
+ResetConflictResolver(Oid subid, char *conflict_type)
+{
+ ConflictType idx;
+ ConflictTypeResolver conflictResolver;

No point in declaring the variable as a ConflictTypeResolver because
the code is only interested in the resolver name field.

~

20.
nit (a) - Add period to code comments
nit (b) - Remove excess blank lines
nit (c) - Change 'conflict_type' to the words 'conflict type' in a
couple of comments

~~~

conf_detection_check_prerequisites:

21.
Why do we get this warning repeated when just setting the same
resolver as the conflict already has? Even overwriting the resolver
default does this.

~~~

SetSubConflictResolver:

22.
nit (a) - Add period to function comment.
nit (b) - Tidy the spaces in the param declaration
nit (c) - Add period in code comments.

~~~

RemoveSubscriptionConflictResolvers:

23.
nit (a) - Add period to function comment.
nit (b) - Minor change to comment wording.
nit (c) - Add period code to comment.

======
src/bin/pg_dump/pg_dump.c
SKIP REVIEW
======
src/bin/pg_dump/pg_dump.h
SKIP REVIEW

======
src/bin/psql/describe.c

24.
+ if (pset.sversion >= 180000)
+ appendPQExpBuffer(&buf,
+   ", (SELECT string_agg(confrtype || ' = ' || confrres, ', ') \n"
+   "   FROM pg_catalog.pg_subscription_conflict c \n"
+   "   WHERE c.confsubid = s.oid) AS \"%s\"\n",
+   gettext_noop("Conflict Resolvers"));

Should this SQL include an ORDER BY so the result is the same each time?

~~~

25. Present the information separately.

The current result is a lot of text, which is not particularly
readable. Even when using the "expanded display" mode.

Name               | sub1
Owner              | postgres
Enabled            | t
Publication        | {pub1}
Binary             | f
Streaming          | off
Two-phase commit   | d
Disable on error   | f
Origin             | any
Password required  | t
Run as owner?      | f
Failover           | f
Synchronous commit | off
Conninfo           | dbname=test_pub
Skip LSN           | 0/0
Conflict Resolvers | insert_exists = error, update_origin_differs =
apply_remote, update_exists = error, update_missing = skip,
delete_origin_differs = apply_remote, delete_missing = skip

I am thinking perhaps the "Conflict Resolver" information should be
presented separately in a list *below* the normal "describe" output.
Other kinds of describe present information this way too (e.g. the
publication lists tables below).

~

26. Identify what was changed by the user

IMO it may also be useful to know which of the conflict resolvers are
the defaults anyway, versus which have been changed to something else
by the user. If you present the information listed separately like
suggested (above) then there would be room to add this additional
info.


======
.../catalog/pg_subscription_conflict.h

27.
+#ifdef CATALOG_VARLEN /* variable-length fields start here */
+ text confrtype BKI_FORCE_NOT_NULL; /* conflict type */
+ text confrres BKI_FORCE_NOT_NULL; /* conflict resolver */
+#endif

These seem strange field names. What do that mean? e.g. for
'confrtype' what is the 'r'?

======
src/include/nodes/parsenodes.h

28.
+ List    *resolvers; /* List of conflict resolvers */
+ char    *conflict_type; /* conflict_type to be reset */
 } AlterSubscriptionStmt;

That 'resolvers' comment should clarify what kind of a list that is.
e.g. Conflict type enums or strings etc.

~~~

29.
+ ALTER_SUBSCRIPTION_CONFLICT_RESOLVERS,
+ ALTER_SUBSCRIPTION_RESET_ALL_CONFLICT_RESOLVERS,
+ ALTER_SUBSCRIPTION_RESET_CONFLICT_RESOLVER,
 } AlterSubscriptionType;

I think these names should mimic the actual syntax more closely.

e.g.
ALTER_SUBSCRIPTION_CONFLICT_RESOLVER,
ALTER_SUBSCRIPTION_CONFLICT_RESOLVER_RESET
ALTER_SUBSCRIPTION_CONFLICT_RESOLVER_RESET_ALL

~~~

30.
+ List    *resolvers; /* List of conflict resolvers */
+ char    *conflict_type; /* conflict_type to be reset */
 } AlterSubscriptionStmt;

30a.
(same as earlier review comment #28)
That 'resolvers' comment should clarify what kind of a list that is.
e.g. Conflict type enums or strings etc.

~

30b.
nit (a) - better name here might be "conflict_type_name", so it is not
confused with the enum
nit (b) - fix the comment: e.g. you can't define 'conflict_type' in
terms of 'conflict_type'

======
src/include/replication/conflict.h

31.
+/* Min and max conflict type */
+#define CT_MIN CT_INSERT_EXISTS
+#define CT_MAX CT_DELETE_MISSING
+
+/*
+ * 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 = 1,

nit - these CT_MIN and CT_MAX are unnecessary. If the enums start at 0
(which they do in C by default anyhow) then you only need to know
'CONFLICT_NUM_TYPES'. Code previously using CT_MIN and CT_MAX will
become simpler too.

~~~

32.
+/*
+ * 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 = 1,
+
+ /* Keep the local change */
+ CR_KEEP_LOCAL,
+
+ /* Apply the remote change; skip if it can not be applied */
+ CR_APPLY_OR_SKIP,
+
+ /* Apply the remote change; emit error if it can not be applied */
+ CR_APPLY_OR_ERROR,
+
+ /* Skip applying the change */
+ CR_SKIP,
+
+ /* Error out */
+ CR_ERROR,
+} ConflictResolver;
+
+/* Min conflict resolver */
+#define CR_MIN CR_APPLY_REMOTE
+#define CR_MAX CR_ERROR
+

nit (a) - /can not/cannot/

nit (b) - as above, those CR_MIN and CR_MAX are not necessary. It is
simpler to start the enum values at 0 and then you only need to know
CONFLICT_NUM_RESOLVERS. Code previously using CT_MIN and CT_MAX will
become simpler too.

(I made these changes already -- see the attachment).

~~~

33.
+typedef struct ConflictTypeResolver
+{
+ const char *conflict_type;
+ const char *resolver;
+} ConflictTypeResolver;

These seem poor field names that are too easily confused with the
enums etc. Let's name them to make it obvious what they really are:

/conflict_type/conflict_type_name/
/resolver/conflict_resolver_name/

~~~

34.
+extern void SetSubConflictResolver(Oid subId, ConflictTypeResolver *
resolvers, int max_types);
+extern void RemoveSubscriptionConflictById(Oid confid);
+extern void RemoveSubscriptionConflictResolvers(Oid confid);
+extern List *GetAndValidateSubsConflictResolverList(List *stmtresolvers);
+extern void UpdateSubConflictResolvers(List *conflict_resolvers, Oid subid);
+extern ConflictType validate_conflict_type_and_resolver(const char
*conflict_type,
+ const char *conflict_resolver);
+extern void SetDefaultResolvers(ConflictTypeResolver * conflictResolvers);
+extern void ResetConflictResolver(Oid subid, char *conflict_type);
+extern void conf_detection_check_prerequisites(void);

nit (a) - remove some spaces that should not be there
nit (b) - make the parameter names more consistent

======
src/test/regress/sql/subscription.sql

35. General - quotes?

Why are all the resolver names single-quoted? (e.g. "update_missing =
'skip'"). AFAIK it is unnecessary.

~

36. General - why not use describe?

Would the text code be easier if you just validated the changes by
using the "describe" code (e.g. \dRs+ regress_testsub) instead of SQL
select of the pg_subscription_conflict table?

~

37.
nit - The test cases seemed mostly good to me. My nits are only for
small changes/typos or clarifications to the comments.

======
Please see the PS nits attachment. It already implements most of the
nits plus some other review comments were suggested above.

======
Kind Regards,
Peter Smith.
Fujitsu Australia
diff --git a/src/backend/commands/subscriptioncmds.c 
b/src/backend/commands/subscriptioncmds.c
index 859bf08..703d313 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -450,10 +450,9 @@ parse_subscription_conflict_resolvers(List *stmtresolvers,
                                                                          
ConflictTypeResolver *resolvers)
 {
        ListCell   *lc;
-       List       *SeenTypes = NIL;
+       bool            already_seen[CONFLICT_NUM_TYPES] = {0};
 
-
-       /* First initialize the resolvers with default values. */
+       /* First, initialize the resolvers with default values. */
        SetDefaultResolvers(resolvers);
 
        foreach(lc, stmtresolvers)
@@ -462,28 +461,26 @@ parse_subscription_conflict_resolvers(List *stmtresolvers,
                ConflictType type;
                char       *resolver;
 
-               /* Check if the conflict type already exists in the list */
-               if (list_member(SeenTypes, makeString(defel->defname)))
-                       ereport(ERROR,
-                                       
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
-                                        errmsg("duplicate conflict type \"%s\" 
found", defel->defname)));
-
                /* Validate the conflict type and resolver. */
                resolver = defGetString(defel);
                type = validate_conflict_type_and_resolver(defel->defname,
                                                                                
                   defGetString(defel));
 
-               /* Add the conflict type to the list of seen types */
-               SeenTypes = lappend(SeenTypes, makeString((char 
*)resolvers[type].conflict_type));
+               /* Check if the conflict type has been already seen. */
+               if (already_seen[type])
+                       ereport(ERROR,
+                                       
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                                        errmsg("duplicate conflict type \"%s\" 
found", defel->defname)));
+
+               already_seen[type] = true;
 
                /* Update the corresponding resolver for the given conflict 
type. */
-               resolvers[type].resolver = 
downcase_truncate_identifier(resolver, strlen(resolver), false);
+               resolvers[type].conflict_resolver_name = 
downcase_truncate_identifier(resolver, strlen(resolver), false);
        }
 
        /* Once validation is complete, warn users if prerequisites are not 
met. */
        if (stmtresolvers)
                conf_detection_check_prerequisites();
-
 }
 
 /*
@@ -630,7 +627,7 @@ CreateSubscription(ParseState *pstate, 
CreateSubscriptionStmt *stmt,
        bits32          supported_opts;
        SubOpts         opts = {0};
        AclResult       aclresult;
-       ConflictTypeResolver conflictResolvers[CONFLICT_NUM_TYPES];
+       ConflictTypeResolver conflict_resolvers[CONFLICT_NUM_TYPES];
 
        /*
         * Parse and check options.
@@ -646,7 +643,7 @@ CreateSubscription(ParseState *pstate, 
CreateSubscriptionStmt *stmt,
        parse_subscription_options(pstate, stmt->options, supported_opts, 
&opts);
 
        /* Parse and check conflict resolvers. */
-       parse_subscription_conflict_resolvers(stmt->resolvers, 
conflictResolvers);
+       parse_subscription_conflict_resolvers(stmt->resolvers, 
conflict_resolvers);
 
        /*
         * Since creating a replication slot is not transactional, rolling back
@@ -774,8 +771,8 @@ CreateSubscription(ParseState *pstate, 
CreateSubscriptionStmt *stmt,
 
        recordDependencyOnOwner(SubscriptionRelationId, subid, owner);
 
-       /* Update the Conflict Resolvers in pg_subscription_conflict */
-       SetSubConflictResolver(subid, conflictResolvers, CONFLICT_NUM_TYPES);
+       /* Update the Conflict Resolvers in pg_subscription_conflict. */
+       SetSubConflictResolver(subid, conflict_resolvers, CONFLICT_NUM_TYPES);
 
        ReplicationOriginNameForLogicalRep(subid, InvalidOid, originname, 
sizeof(originname));
        replorigin_create(originname);
@@ -1639,11 +1636,10 @@ AlterSubscription(ParseState *pstate, 
AlterSubscriptionStmt *stmt,
                        {
                                List       *conflict_resolvers = NIL;
 
-
                                /* Get the list of conflict types and resolvers 
and validate them. */
                                conflict_resolvers = 
GetAndValidateSubsConflictResolverList(stmt->resolvers);
 
-                               /* Warn users if prerequisites are not met */
+                               /* Warn users if prerequisites are not met. */
                                conf_detection_check_prerequisites();
 
                                /*
@@ -1655,7 +1651,7 @@ AlterSubscription(ParseState *pstate, 
AlterSubscriptionStmt *stmt,
                        }
                case ALTER_SUBSCRIPTION_RESET_ALL_CONFLICT_RESOLVERS:
                        {
-                               ConflictTypeResolver 
conflictResolvers[CONFLICT_NUM_TYPES];
+                               ConflictTypeResolver 
conflict_resolvers[CONFLICT_NUM_TYPES];
 
                                /* Remove the existing conflict resolvers. */
                                RemoveSubscriptionConflictResolvers(subid);
@@ -1664,8 +1660,8 @@ AlterSubscription(ParseState *pstate, 
AlterSubscriptionStmt *stmt,
                                 * Create list of conflict resolvers and set 
them in the
                                 * catalog.
                                 */
-                               SetDefaultResolvers(conflictResolvers);
-                               SetSubConflictResolver(subid, 
conflictResolvers, CONFLICT_NUM_TYPES);
+                               SetDefaultResolvers(conflict_resolvers);
+                               SetSubConflictResolver(subid, 
conflict_resolvers, CONFLICT_NUM_TYPES);
                                break;
                        }
                case ALTER_SUBSCRIPTION_RESET_CONFLICT_RESOLVER:
@@ -1674,7 +1670,7 @@ AlterSubscription(ParseState *pstate, 
AlterSubscriptionStmt *stmt,
                                 * Reset the conflict resolver for this 
conflict type to its
                                 * default.
                                 */
-                               ResetConflictResolver(subid, 
stmt->conflict_type);
+                               ResetConflictResolver(subid, 
stmt->conflict_type_name);
                                break;
                        }
 
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 8b7023d..e94c91f 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -10870,10 +10870,11 @@ AlterSubscriptionStmt:
 
                                        n->kind = 
ALTER_SUBSCRIPTION_RESET_CONFLICT_RESOLVER;
                                        n->subname = $3;
-                                       n->conflict_type = $8;
+                                       n->conflict_type_name = $8;
                                        $$ = (Node *) n;
                                }
                ;
+
 conflict_type:
                        Sconst                                                  
        { $$ = $1; }
                ;
diff --git a/src/backend/replication/logical/conflict.c 
b/src/backend/replication/logical/conflict.c
index ff44c3c..a75f478 100644
--- a/src/backend/replication/logical/conflict.c
+++ b/src/backend/replication/logical/conflict.c
@@ -40,6 +40,9 @@ static const char *const ConflictTypeNames[] = {
        [CT_DELETE_MISSING] = "delete_missing"
 };
 
+StaticAssertDecl(lengthof(ConflictTypeNames) == CONFLICT_NUM_TYPES,
+                                "array length mismatch");
+
 static const char *const ConflictResolverNames[] = {
        [CR_APPLY_REMOTE] = "apply_remote",
        [CR_KEEP_LOCAL] = "keep_local",
@@ -49,7 +52,8 @@ static const char *const ConflictResolverNames[] = {
        [CR_ERROR] = "error"
 };
 
-#define CONFLICT_TYPE_MAX_RESOLVERS 4
+StaticAssertDecl(lengthof(ConflictResolverNames) == CONFLICT_NUM_RESOLVERS,
+                                "array length mismatch");
 
 /*
  * Valid conflict resolvers for each conflict type.
@@ -66,28 +70,23 @@ static const char *const ConflictResolverNames[] = {
  * 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.
+ *
+ * NOTES:
+ * The first resolver (i.e. resolver at index [0]) is the default
+ * resolver for each conflict type. There is a -1 end marker for each list
+ * valid resolvers.
  */
-static const int ConflictTypeResolverMap[][CONFLICT_TYPE_MAX_RESOLVERS] = {
-       [CT_INSERT_EXISTS] = {CR_APPLY_REMOTE, CR_KEEP_LOCAL, CR_ERROR},
-       [CT_UPDATE_EXISTS] = {CR_APPLY_REMOTE, CR_KEEP_LOCAL, CR_ERROR},
-       [CT_UPDATE_ORIGIN_DIFFERS] = {CR_APPLY_REMOTE, CR_KEEP_LOCAL, CR_ERROR},
-       [CT_UPDATE_MISSING] = {CR_APPLY_OR_SKIP, CR_APPLY_OR_ERROR, CR_SKIP, 
CR_ERROR},
-       [CT_DELETE_MISSING] = {CR_SKIP, CR_ERROR},
-       [CT_DELETE_ORIGIN_DIFFERS] = {CR_APPLY_REMOTE, CR_KEEP_LOCAL, CR_ERROR}
+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}
 };
 
-/*
- * Default conflict resolver for each conflict type.
- */
-static const int ConflictTypeDefaultResolvers[] = {
-       [CT_INSERT_EXISTS] = CR_ERROR,
-       [CT_UPDATE_EXISTS] = CR_ERROR,
-       [CT_UPDATE_ORIGIN_DIFFERS] = CR_APPLY_REMOTE,
-       [CT_UPDATE_MISSING] = CR_SKIP,
-       [CT_DELETE_MISSING] = CR_SKIP,
-       [CT_DELETE_ORIGIN_DIFFERS] = CR_APPLY_REMOTE
-
-};
+StaticAssertDecl(lengthof(ConflictTypeResolverMap) == CONFLICT_NUM_TYPES,
+                                "array length mismatch");
 
 static int     errcode_apply_conflict(ConflictType type);
 static int     errdetail_apply_conflict(EState *estate,
@@ -547,20 +546,18 @@ build_index_value_desc(EState *estate, Relation localrel, 
TupleTableSlot *slot,
        return index_value;
 }
 
-
 /*
- * Set default values for CONFLICT RESOLVERS for each conflict type
+ * Set default values for CONFLICT RESOLVERS for each conflict type.
  */
 void
-SetDefaultResolvers(ConflictTypeResolver * conflictResolvers)
+SetDefaultResolvers(ConflictTypeResolver *resolvers)
 {
-       ConflictType type;
-
-       for (type = CT_MIN; type <= CT_MAX; type++)
+       for (ConflictType type = 0; type < CONFLICT_NUM_TYPES; type++)
        {
-               conflictResolvers[type].conflict_type = ConflictTypeNames[type];
-               conflictResolvers[type].resolver =
-                       
ConflictResolverNames[ConflictTypeDefaultResolvers[type]];
+               ConflictResolver def_resolver = 
ConflictTypeResolverMap[type][0];
+
+               resolvers[type].conflict_type_name = ConflictTypeNames[type];
+               resolvers[type].conflict_resolver_name = 
ConflictResolverNames[def_resolver];
        }
 }
 
@@ -575,10 +572,9 @@ validate_conflict_type_and_resolver(const char 
*conflict_type,
        ConflictType type;
        ConflictResolver resolver;
        bool            valid = false;
-       int                     i;
 
-       /* Check conflict type validity */
-       for (type = CT_MIN; type <= CT_MAX; type++)
+       /* Validate the conflict type name. */
+       for (type = 0; type < CONFLICT_NUM_TYPES; type++)
        {
                if (pg_strcasecmp(ConflictTypeNames[type], conflict_type) == 0)
                {
@@ -595,8 +591,8 @@ validate_conflict_type_and_resolver(const char 
*conflict_type,
        /* Reset */
        valid = false;
 
-       /* Check conflict resolver validity. */
-       for (resolver = CR_MIN; resolver <= CR_MAX; resolver++)
+       /* Validate the conflict resolver name. */
+       for (resolver = 0; resolver < CONFLICT_NUM_RESOLVERS; resolver++)
        {
                if (pg_strcasecmp(ConflictResolverNames[resolver], 
conflict_resolver) == 0)
                {
@@ -614,9 +610,17 @@ validate_conflict_type_and_resolver(const char 
*conflict_type,
        valid = false;
 
        /* Check if conflict resolver is a valid one for the given conflict 
type */
-       for (i = 0; i < CONFLICT_TYPE_MAX_RESOLVERS; i++)
+       for (int i = 0; i < CONFLICT_NUM_RESOLVERS; i++)
        {
-               if (ConflictTypeResolverMap[type][i] == resolver)
+               int candidate = ConflictTypeResolverMap[type][i];
+
+               if (candidate < 0)
+               {
+                       /* No more possible resolvers for this conflict type. */
+                       break;
+               }
+
+               if (candidate == resolver)
                {
                        valid = true;
                        break;
@@ -629,51 +633,48 @@ validate_conflict_type_and_resolver(const char 
*conflict_type,
                                errmsg("%s is not a valid conflict resolver for 
conflict type %s",
                                           conflict_resolver,
                                           conflict_type));
-       return type;
 
+       return type;
 }
 
 /*
- * Extract the conflict type and conflict resolvers from the
+ * Extract the conflict types and conflict resolvers from the
  * ALTER SUBSCRIPTION command and return a list of ConflictTypeResolver nodes.
  */
 List *
 GetAndValidateSubsConflictResolverList(List *stmtresolvers)
 {
-       ConflictTypeResolver *conftyperesolver = NULL;
        List       *res = NIL;
-       List       *conflictTypes = NIL;
+       bool            already_seen[CONFLICT_NUM_TYPES] = {0};
 
        foreach_ptr(DefElem, defel, stmtresolvers)
        {
+               ConflictType conflict_type;
+               ConflictTypeResolver *conftyperesolver;
                char       *resolver_str;
 
-               /* Check if the conflict type already exists in the list */
-               if (list_member(conflictTypes, makeString(defel->defname)))
-               {
-                       ereport(ERROR,
-                                       
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
-                                        errmsg("duplicate conflict type \"%s\" 
found", defel->defname)));
-               }
-
                conftyperesolver = palloc(sizeof(ConflictTypeResolver));
-               conftyperesolver->conflict_type = 
downcase_truncate_identifier(defel->defname,
+               conftyperesolver->conflict_type_name = 
downcase_truncate_identifier(defel->defname,
                                                                                
        strlen(defel->defname), false);
                resolver_str = defGetString(defel);
-               conftyperesolver->resolver = 
downcase_truncate_identifier(resolver_str,
+               conftyperesolver->conflict_resolver_name = 
downcase_truncate_identifier(resolver_str,
                                                                                
strlen(resolver_str), false);
 
                /*
-                * Validate the conflict type and that the resolver is valid 
for that
-                * conflict type
+                * Validate the conflict type, and check the resolver is valid 
for that
+                * conflict type.
                 */
-               validate_conflict_type_and_resolver(
-                                                                               
conftyperesolver->conflict_type,
-                                                                               
conftyperesolver->resolver);
+               conflict_type = validate_conflict_type_and_resolver(
+                                                                               
conftyperesolver->conflict_type_name,
+                                                                               
conftyperesolver->conflict_resolver_name);
+
+               /* Check if the conflict type has been seen already. */
+               if (already_seen[conflict_type])
+                       ereport(ERROR,
+                                       
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                                        errmsg("duplicate conflict type \"%s\" 
found", conftyperesolver->conflict_type_name)));
 
-               /* Add the conflict type to the list of seen types */
-               conflictTypes = lappend(conflictTypes,
-                                                               
makeString((char *) conftyperesolver->conflict_type));
+               already_seen[conflict_type] = true;
 
                /* Add the validated ConflictTypeResolver to the result list */
                res = lappend(res, conftyperesolver);
@@ -711,7 +712,7 @@ UpdateSubConflictResolvers(List *conflict_resolvers, Oid 
subid)
                /* set up subid and conflict_type to search in cache */
                values[Anum_pg_subscription_conflict_confsubid - 1] = 
ObjectIdGetDatum(subid);
                values[Anum_pg_subscription_conflict_confrtype - 1] =
-                       CStringGetTextDatum(conftyperesolver->conflict_type);
+                       
CStringGetTextDatum(conftyperesolver->conflict_type_name);
 
                oldtup = SearchSysCache2(SUBSCRIPTIONCONFLMAP,
                                                                 
values[Anum_pg_subscription_conflict_confsubid - 1],
@@ -721,7 +722,7 @@ UpdateSubConflictResolvers(List *conflict_resolvers, Oid 
subid)
                {
                        /* Update the new resolver */
                        values[Anum_pg_subscription_conflict_confrres - 1] =
-                                               
CStringGetTextDatum(conftyperesolver->resolver);
+                                               
CStringGetTextDatum(conftyperesolver->conflict_resolver_name);
                        replaces[Anum_pg_subscription_conflict_confrres - 1] = 
true;
 
                        newtup = heap_modify_tuple(oldtup, 
RelationGetDescr(pg_subscription_conflict),
@@ -732,7 +733,7 @@ UpdateSubConflictResolvers(List *conflict_resolvers, Oid 
subid)
                }
                else
                        elog(ERROR, "cache lookup failed for table conflict %s 
for subid %u",
-                                conftyperesolver->conflict_type, subid);
+                                conftyperesolver->conflict_type_name, subid);
 
        }
 
@@ -746,7 +747,7 @@ void
 ResetConflictResolver(Oid subid, char *conflict_type)
 {
        ConflictType idx;
-       ConflictTypeResolver conflictResolver;
+       const char *resolver_name;
        Datum           values[Natts_pg_subscription_conflict];
        bool            nulls[Natts_pg_subscription_conflict];
        bool            replaces[Natts_pg_subscription_conflict];
@@ -757,8 +758,8 @@ ResetConflictResolver(Oid subid, char *conflict_type)
        char       *cur_conflict_res;
        Datum           datum;
 
-       /* Get the index for this conflict_type */
-       for (idx = CT_MIN; idx <= CT_MAX; idx++)
+       /* Get the index for this conflict type. */
+       for (idx = 0; idx < CONFLICT_NUM_TYPES; idx++)
        {
                if (pg_strcasecmp(ConflictTypeNames[idx], conflict_type) == 0)
                {
@@ -772,8 +773,8 @@ ResetConflictResolver(Oid subid, char *conflict_type)
                                errcode(ERRCODE_INVALID_PARAMETER_VALUE),
                                errmsg("%s is not a valid conflict type", 
conflict_type));
 
-       /* Get the default resolver for this conflict_type. */
-       conflictResolver.resolver = 
ConflictResolverNames[ConflictTypeDefaultResolvers[idx]];
+       /* Get the default resolver for this conflict type. */
+       resolver_name = ConflictResolverNames[ConflictTypeResolverMap[idx][0]];
 
        /* Prepare to update a tuple. */
        memset(nulls, false, sizeof(nulls));
@@ -789,7 +790,6 @@ ResetConflictResolver(Oid subid, char *conflict_type)
                                                         
values[Anum_pg_subscription_conflict_confsubid - 1],
                                                         
values[Anum_pg_subscription_conflict_confrtype - 1]);
 
-
        if (!HeapTupleIsValid(oldtup))
                elog(ERROR, "cache lookup failed for table conflict %s for 
subid %u", conflict_type, subid);
 
@@ -798,11 +798,11 @@ ResetConflictResolver(Oid subid, char *conflict_type)
        cur_conflict_res = TextDatumGetCString(datum);
 
        /* Check if current resolver is the default one, if not update it. */
-       if (pg_strcasecmp(cur_conflict_res, conflictResolver.resolver) != 0)
+       if (pg_strcasecmp(cur_conflict_res, resolver_name) != 0)
        {
-               /* Update the new resolver */
+               /* Update the new resolver. */
                values[Anum_pg_subscription_conflict_confrres - 1] =
-                       CStringGetTextDatum(conflictResolver.resolver);
+                       CStringGetTextDatum(resolver_name);
                replaces[Anum_pg_subscription_conflict_confrres - 1] = true;
 
                newtup = heap_modify_tuple(oldtup, 
RelationGetDescr(pg_subscription_conflict),
@@ -832,10 +832,10 @@ conf_detection_check_prerequisites(void)
 }
 
 /*
- * Set Conflict Resolvers on the subscription
+ * Set Conflict Resolvers on the subscription.
  */
 void
-SetSubConflictResolver(Oid subId, ConflictTypeResolver * resolvers, int 
resolvers_cnt)
+SetSubConflictResolver(Oid subId, ConflictTypeResolver *resolvers, int 
resolvers_cnt)
 {
        Relation        pg_subscription_conflict;
        Datum           values[Natts_pg_subscription_conflict];
@@ -853,11 +853,11 @@ SetSubConflictResolver(Oid subId, ConflictTypeResolver * 
resolvers, int resolver
        {
                values[Anum_pg_subscription_conflict_confsubid - 1] = 
ObjectIdGetDatum(subId);
                values[Anum_pg_subscription_conflict_confrtype - 1] =
-                       CStringGetTextDatum(resolvers[idx].conflict_type);
+                       CStringGetTextDatum(resolvers[idx].conflict_type_name);
                values[Anum_pg_subscription_conflict_confrres - 1] =
-                       CStringGetTextDatum(resolvers[idx].resolver);
+                       
CStringGetTextDatum(resolvers[idx].conflict_resolver_name);
 
-               /* Get a new oid and update the tuple into catalog */
+               /* 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);
@@ -871,7 +871,7 @@ SetSubConflictResolver(Oid subId, ConflictTypeResolver * 
resolvers, int resolver
 }
 
 /*
- * Remove the subscription conflict resolvers for the subscription id
+ * Remove the subscription conflict resolvers for the subscription id.
  */
 void
 RemoveSubscriptionConflictResolvers(Oid subid)
@@ -884,8 +884,8 @@ RemoveSubscriptionConflictResolvers(Oid subid)
        rel = table_open(SubscriptionConflictId, RowExclusiveLock);
 
        /*
-        * Search using the subid, this should return all conflict resolvers for
-        * this sub
+        * Search using the subid to return all conflict resolvers for
+        * this subscription.
         */
        ScanKeyInit(&skey[0],
                                Anum_pg_subscription_conflict_confsubid,
@@ -895,7 +895,7 @@ RemoveSubscriptionConflictResolvers(Oid subid)
 
        scan = table_beginscan_catalog(rel, 1, skey);
 
-       /* Iterate through the tuples and delete them */
+       /* Iterate through the tuples and delete them. */
        while (HeapTupleIsValid(tup = heap_getnext(scan, ForwardScanDirection)))
                CatalogTupleDelete(rel, &tup->t_self);
 
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 7e19e0c..0b2832d 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -4228,7 +4228,7 @@ typedef struct AlterSubscriptionStmt
        List       *publication;        /* One or more publication to subscribe 
to */
        List       *options;            /* List of DefElem nodes */
        List       *resolvers;          /* List of conflict resolvers */
-       char       *conflict_type;      /* conflict_type to be reset */
+       char       *conflict_type_name; /* Name of the conflict type to be 
reset */
 } AlterSubscriptionStmt;
 
 typedef struct DropSubscriptionStmt
diff --git a/src/include/replication/conflict.h 
b/src/include/replication/conflict.h
index fcd49da..c5c3c20 100644
--- a/src/include/replication/conflict.h
+++ b/src/include/replication/conflict.h
@@ -50,10 +50,6 @@ typedef enum
 
 #define CONFLICT_NUM_TYPES (CT_DELETE_MISSING + 1)
 
-/* Min and max conflict type */
-#define CT_MIN CT_INSERT_EXISTS
-#define CT_MAX CT_DELETE_MISSING
-
 /*
  * Conflict resolvers that can be used to resolve various conflicts.
  *
@@ -63,15 +59,15 @@ typedef enum
 typedef enum ConflictResolver
 {
        /* Apply the remote change */
-       CR_APPLY_REMOTE = 1,
+       CR_APPLY_REMOTE,
 
        /* Keep the local change */
        CR_KEEP_LOCAL,
 
-       /* Apply the remote change; skip if it can not be applied */
+       /* Apply the remote change; skip if it cannot be applied */
        CR_APPLY_OR_SKIP,
 
-       /* Apply the remote change; emit error if it can not be applied */
+       /* Apply the remote change; emit error if it cannot be applied */
        CR_APPLY_OR_ERROR,
 
        /* Skip applying the change */
@@ -81,14 +77,12 @@ typedef enum ConflictResolver
        CR_ERROR,
 } ConflictResolver;
 
-/* Min conflict resolver */
-#define CR_MIN CR_APPLY_REMOTE
-#define CR_MAX CR_ERROR
+#define CONFLICT_NUM_RESOLVERS (CR_ERROR + 1)
 
 typedef struct ConflictTypeResolver
 {
-       const char *conflict_type;
-       const char *resolver;
+       const char *conflict_type_name;
+       const char *conflict_resolver_name;
 }                      ConflictTypeResolver;
 
 extern bool GetTupleTransactionInfo(TupleTableSlot *localslot,
@@ -103,14 +97,14 @@ extern void ReportApplyConflict(EState *estate, 
ResultRelInfo *relinfo,
                                                                Oid indexoid, 
TransactionId localxmin,
                                                                RepOriginId 
localorigin, TimestampTz localts);
 extern void InitConflictIndexes(ResultRelInfo *relInfo);
-extern void SetSubConflictResolver(Oid subId, ConflictTypeResolver * 
resolvers, int max_types);
+extern void SetSubConflictResolver(Oid subId, ConflictTypeResolver *resolvers, 
int max_types);
 extern void RemoveSubscriptionConflictById(Oid confid);
 extern void RemoveSubscriptionConflictResolvers(Oid confid);
 extern List *GetAndValidateSubsConflictResolverList(List *stmtresolvers);
 extern void UpdateSubConflictResolvers(List *conflict_resolvers, Oid subid);
 extern ConflictType validate_conflict_type_and_resolver(const char 
*conflict_type,
                                                                                
                                const char *conflict_resolver);
-extern void SetDefaultResolvers(ConflictTypeResolver * conflictResolvers);
+extern void SetDefaultResolvers(ConflictTypeResolver *resolvers);
 extern void ResetConflictResolver(Oid subid, char *conflict_type);
 extern void conf_detection_check_prerequisites(void);
 
diff --git a/src/test/regress/expected/subscription.out 
b/src/test/regress/expected/subscription.out
index 9fa2a3f..c62c745 100644
--- a/src/test/regress/expected/subscription.out
+++ b/src/test/regress/expected/subscription.out
@@ -407,14 +407,14 @@ ERROR:  foo is not a valid conflict resolver
 -- fail - invalid conflict types
 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 resolver for that conflict type
+-- fail - invalid resolver for given 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 types
 CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' 
PUBLICATION testpub CONFLICT RESOLVER (insert_exists = 'keep_local', 
insert_exists = 'keep_local');
 ERROR:  duplicate conflict type "insert_exists" found
--- creating subscription should create default conflict resolvers
+-- ok - create subscription with no conflict resolvers should create defaults
 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.
@@ -431,13 +431,13 @@ SELECT confrtype, confrres FROM pg_subscription_conflict 
ORDER BY confrtype;
 
 ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
 DROP SUBSCRIPTION regress_testsub;
--- ok - valid conflict type and resolvers
+-- ok - create subscription specifying valid conflict type 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
+--check if above are configured; for non-specified conflict types, default 
resolvers should be seen
 SELECT confrtype, confrres FROM pg_subscription_conflict ORDER BY confrtype;
        confrtype       |   confrres   
 -----------------------+--------------
@@ -449,16 +449,16 @@ SELECT confrtype, confrres FROM pg_subscription_conflict 
ORDER BY confrtype;
  update_origin_differs | apply_remote
 (6 rows)
 
--- fail - altering with invalid conflict type
+-- fail - alter 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
+-- fail - alter 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 types
+-- fail - alter with duplicate conflict types
 ALTER SUBSCRIPTION regress_testsub CONFLICT RESOLVER (insert_exists = 
'apply_remote', insert_exists = 'apply_remote');
 ERROR:  duplicate conflict type "insert_exists" found
--- ok - valid conflict types and resolvers
+-- ok - alter to set 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.
@@ -473,7 +473,7 @@ SELECT confrtype, confrres FROM pg_subscription_conflict 
ORDER BY confrtype;
  update_origin_differs | apply_remote
 (6 rows)
 
--- ok - valid conflict types and resolvers
+-- ok - alter to set 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.
@@ -488,10 +488,10 @@ SELECT confrtype, confrres FROM pg_subscription_conflict 
ORDER BY confrtype;
  update_origin_differs | error
 (6 rows)
 
--- fail - reset with an invalid conflit type
+-- fail - alter to reset an invalid conflict type
 ALTER SUBSCRIPTION regress_testsub RESET CONFLICT RESOLVER for 'foo';
 ERROR:  foo is not a valid conflict type
--- ok - valid conflict type
+-- ok - alter to reset a valid conflict type
 ALTER SUBSCRIPTION regress_testsub RESET CONFLICT RESOLVER for 'insert_exists';
 SELECT confrtype, confrres FROM pg_subscription_conflict ORDER BY confrtype;
        confrtype       |  confrres  
@@ -504,7 +504,7 @@ SELECT confrtype, confrres FROM pg_subscription_conflict 
ORDER BY confrtype;
  update_origin_differs | error
 (6 rows)
 
--- ok - reset ALL
+-- ok - alter to reset conflict resolvers for all conflict types
 ALTER SUBSCRIPTION regress_testsub RESET CONFLICT RESOLVER ALL;
 SELECT confrtype, confrres FROM pg_subscription_conflict ORDER BY confrtype;
        confrtype       |   confrres   
diff --git a/src/test/regress/sql/subscription.sql 
b/src/test/regress/sql/subscription.sql
index 596f2e1..b081c1d 100644
--- a/src/test/regress/sql/subscription.sql
+++ b/src/test/regress/sql/subscription.sql
@@ -278,51 +278,50 @@ CREATE SUBSCRIPTION regress_testsub CONNECTION 
'dbname=regress_doesnotexist' PUB
 -- fail - invalid conflict types
 CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' 
PUBLICATION testpub CONFLICT RESOLVER (foo = 'keep_local') WITH (connect = 
false);
 
--- fail - invalid resolver for that conflict type
+-- fail - invalid resolver for given conflict type
 CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' 
PUBLICATION testpub
  CONFLICT RESOLVER (update_missing = 'apply_remote') WITH (connect = false);
 
 -- fail - duplicate conflict types
 CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' 
PUBLICATION testpub CONFLICT RESOLVER (insert_exists = 'keep_local', 
insert_exists = 'keep_local');
 
--- creating subscription should create default conflict resolvers
+-- ok - create subscription with no conflict resolvers should create defaults
 CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' 
PUBLICATION testpub WITH (connect = false);
 SELECT confrtype, confrres FROM pg_subscription_conflict ORDER BY confrtype;
 
 ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
 DROP SUBSCRIPTION regress_testsub;
 
--- ok - valid conflict type and resolvers
+-- ok - create subscription specifying valid conflict type 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
+--check if above are configured; for non-specified conflict types, default 
resolvers should be seen
 SELECT confrtype, confrres FROM pg_subscription_conflict ORDER BY confrtype;
 
--- fail - altering with invalid conflict type
+-- fail - alter with invalid conflict type
 ALTER SUBSCRIPTION regress_testsub CONFLICT RESOLVER (foo = 'keep_local');
 
--- fail - altering with invalid conflict resolver
+-- fail - alter with invalid conflict resolver
 ALTER SUBSCRIPTION regress_testsub CONFLICT RESOLVER (insert_exists = 'foo');
 
--- fail - altering with duplicate conflict types
+-- fail - alter with duplicate conflict types
 ALTER SUBSCRIPTION regress_testsub CONFLICT RESOLVER (insert_exists = 
'apply_remote', insert_exists = 'apply_remote');
 
--- ok - valid conflict types and resolvers
+-- ok - alter to set valid conflict types and resolvers
 ALTER SUBSCRIPTION regress_testsub CONFLICT RESOLVER (insert_exists = 
'apply_remote', update_missing = 'skip', delete_origin_differs = 'keep_local' );
 SELECT confrtype, confrres FROM pg_subscription_conflict ORDER BY confrtype;
 
--- ok - valid conflict types and resolvers
+-- ok - alter to set valid conflict types and resolvers
 ALTER SUBSCRIPTION regress_testsub CONFLICT RESOLVER (update_exists = 
'keep_local', delete_missing = 'error', update_origin_differs = 'error');
 SELECT confrtype, confrres FROM pg_subscription_conflict ORDER BY confrtype;
 
--- fail - reset with an invalid conflit type
+-- fail - alter to reset an invalid conflict type
 ALTER SUBSCRIPTION regress_testsub RESET CONFLICT RESOLVER for 'foo';
 
--- ok - valid conflict type
+-- ok - alter to reset a valid conflict type
 ALTER SUBSCRIPTION regress_testsub RESET CONFLICT RESOLVER for 'insert_exists';
 SELECT confrtype, confrres FROM pg_subscription_conflict ORDER BY confrtype;
 
--- ok - reset ALL
+-- ok - alter to reset conflict resolvers for all conflict types
 ALTER SUBSCRIPTION regress_testsub RESET CONFLICT RESOLVER ALL;
 SELECT confrtype, confrres FROM pg_subscription_conflict ORDER BY confrtype;
 

Reply via email to