Add a failover option to subscriptions.
authorAmit Kapila <akapila@postgresql.org>
Tue, 30 Jan 2024 11:01:09 +0000 (16:31 +0530)
committerAmit Kapila <akapila@postgresql.org>
Tue, 30 Jan 2024 11:19:28 +0000 (16:49 +0530)
This commit introduces a new subscription option named 'failover', which
provides users with the ability to set the failover property of the
replication slot on the publisher when creating or altering a
subscription.

This uses the replication commands introduced by commit 7329240437 to
enable the failover option for a logical replication slot.

If the failover option is set to true, the associated replication slots
(i.e. the main slot and the table sync slots) in the upstream database are
enabled to be synchronized to the standbys. Note that the capability to
sync the replication slots will be added in subsequent commits.

Thanks to Masahiko Sawada for the design inputs.

Author: Shveta Malik, Hou Zhijie, Ajin Cherian
Reviewed-by: Peter Smith, Bertrand Drouvot, Dilip Kumar, Masahiko Sawada, Nisha Moond, Kuroda Hayato, Amit Kapila
Discussion: https://postgr.es/m/514f6f2f-6833-4539-39f1-96cd1e011f23@enterprisedb.com

21 files changed:
doc/src/sgml/catalogs.sgml
doc/src/sgml/ref/alter_subscription.sgml
doc/src/sgml/ref/create_subscription.sgml
doc/src/sgml/ref/pg_dump.sgml
src/backend/catalog/pg_subscription.c
src/backend/catalog/system_views.sql
src/backend/commands/subscriptioncmds.c
src/backend/replication/logical/tablesync.c
src/backend/replication/logical/worker.c
src/bin/pg_dump/pg_dump.c
src/bin/pg_dump/pg_dump.h
src/bin/pg_upgrade/t/003_logical_slots.pl
src/bin/pg_upgrade/t/004_subscription.pl
src/bin/psql/describe.c
src/bin/psql/tab-complete.c
src/include/catalog/catversion.h
src/include/catalog/pg_subscription.h
src/test/recovery/meson.build
src/test/recovery/t/040_standby_failover_slots_sync.pl [new file with mode: 0644]
src/test/regress/expected/subscription.out
src/test/regress/sql/subscription.sql

index 16b94461b2ce0ab43636a7ff03a5ac0ca836ce54..880f717b103577b86c21b1a9916e3f6624cfe942 100644 (file)
@@ -8000,6 +8000,17 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
       </para></entry>
      </row>
 
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>subfailover</structfield> <type>bool</type>
+      </para>
+      <para>
+       If true, the associated replication slots (i.e. the main slot and the
+       table sync slots) in the upstream database are enabled to be
+       synchronized to the standbys
+      </para></entry>
+     </row>
+
      <row>
       <entry role="catalog_table_entry"><para role="column_definition">
        <structfield>subconninfo</structfield> <type>text</type>
index 6d36ff0dc90a34b17a61314733af92079845bdae..e9e6d9d74ad92773d6b2056df5601c38683f97bf 100644 (file)
@@ -226,10 +226,31 @@ ALTER SUBSCRIPTION <replaceable class="parameter">name</replaceable> RENAME TO <
       <link linkend="sql-createsubscription-params-with-streaming"><literal>streaming</literal></link>,
       <link linkend="sql-createsubscription-params-with-disable-on-error"><literal>disable_on_error</literal></link>,
       <link linkend="sql-createsubscription-params-with-password-required"><literal>password_required</literal></link>,
-      <link linkend="sql-createsubscription-params-with-run-as-owner"><literal>run_as_owner</literal></link>, and
-      <link linkend="sql-createsubscription-params-with-origin"><literal>origin</literal></link>.
+      <link linkend="sql-createsubscription-params-with-run-as-owner"><literal>run_as_owner</literal></link>,
+      <link linkend="sql-createsubscription-params-with-origin"><literal>origin</literal></link>, and
+      <link linkend="sql-createsubscription-params-with-failover"><literal>failover</literal></link>.
       Only a superuser can set <literal>password_required = false</literal>.
      </para>
+
+     <para>
+      When altering the
+      <link linkend="sql-createsubscription-params-with-slot-name"><literal>slot_name</literal></link>,
+      the <literal>failover</literal> property value of the named slot may differ from the
+      <link linkend="sql-createsubscription-params-with-failover"><literal>failover</literal></link>
+      parameter specified in the subscription. When creating the slot,
+      ensure the slot <literal>failover</literal> property matches the
+      <link linkend="sql-createsubscription-params-with-failover"><literal>failover</literal></link>
+      parameter value of the subscription. Otherwise, the slot on the
+      publisher may behave differently from what subscription's
+      <link linkend="sql-createsubscription-params-with-failover"><literal>failover</literal></link>
+      option says. The slot on the publisher could either be
+      synced to the standbys even when the subscription's
+      <link linkend="sql-createsubscription-params-with-failover"><literal>failover</literal></link>
+      option is disabled or could be disabled for sync
+      even when the subscription's
+      <link linkend="sql-createsubscription-params-with-failover"><literal>failover</literal></link>
+      option is enabled.
+     </para>
     </listitem>
    </varlistentry>
 
index c7ace922f92571e25e03556af51a073c949746d3..ee89ffb1d1247051ee4d90fe9e78ed0445553692 100644 (file)
@@ -117,19 +117,22 @@ CREATE SUBSCRIPTION <replaceable class="parameter">subscription_name</replaceabl
           command should connect to the publisher at all.  The default
           is <literal>true</literal>.  Setting this to
           <literal>false</literal> will force the values of
-          <literal>create_slot</literal>, <literal>enabled</literal> and
-          <literal>copy_data</literal> to <literal>false</literal>.
+          <literal>create_slot</literal>, <literal>enabled</literal>,
+          <literal>copy_data</literal>, and <literal>failover</literal>
+          to <literal>false</literal>.
           (You cannot combine setting <literal>connect</literal>
           to <literal>false</literal> with
           setting <literal>create_slot</literal>, <literal>enabled</literal>,
-          or <literal>copy_data</literal> to <literal>true</literal>.)
+          <literal>copy_data</literal>, or <literal>failover</literal> to
+          <literal>true</literal>.)
          </para>
 
          <para>
           Since no connection is made when this option is
           <literal>false</literal>, no tables are subscribed. To initiate
           replication, you must manually create the replication slot, enable
-          the subscription, and refresh the subscription. See
+          the failover if required, enable the subscription, and refresh the
+          subscription. See
           <xref linkend="logical-replication-subscription-examples-deferred-slot"/>
           for examples.
          </para>
@@ -400,6 +403,18 @@ CREATE SUBSCRIPTION <replaceable class="parameter">subscription_name</replaceabl
          </para>
         </listitem>
        </varlistentry>
+
+       <varlistentry id="sql-createsubscription-params-with-failover">
+        <term><literal>failover</literal> (<type>boolean</type>)</term>
+        <listitem>
+         <para>
+          Specifies whether the replication slots associated with the subscription
+          are enabled to be synced to the standbys so that logical
+          replication can be resumed from the new primary after failover.
+          The default is <literal>false</literal>.
+         </para>
+        </listitem>
+       </varlistentry>
       </variablelist></para>
 
     </listitem>
index 0e5ba4f7125e26f80257e7c5da31fa0ca443c80c..f8ae4220e1dec2d674f97122a36519e875eb0da3 100644 (file)
@@ -1588,7 +1588,13 @@ CREATE DATABASE foo WITH TEMPLATE template0;
    dump can be restored without requiring network access to the remote
    servers.  It is then up to the user to reactivate the subscriptions in a
    suitable way.  If the involved hosts have changed, the connection
-   information might have to be changed.  It might also be appropriate to
+   information might have to be changed.  If the subscription needs to
+   be enabled for
+   <link linkend="sql-createsubscription-params-with-failover"><literal>failover</literal></link>,
+   then same needs to be done by executing
+   <link linkend="sql-altersubscription-params-set">
+   <literal>ALTER SUBSCRIPTION ... SET (failover = true)</literal></link>
+   after the slot has been created.  It might also be appropriate to
    truncate the target tables before initiating a new full table copy.  If users
    intend to copy initial data during refresh they must create the slot with
    <literal>two_phase = false</literal>.  After the initial sync, the
index c516c25ac7b64e6fa1b0c60d0857d2f7f3127d41..406a3c2dd156e324835eeb9059cd574d3835f15c 100644 (file)
@@ -73,6 +73,7 @@ GetSubscription(Oid subid, bool missing_ok)
        sub->disableonerr = subform->subdisableonerr;
        sub->passwordrequired = subform->subpasswordrequired;
        sub->runasowner = subform->subrunasowner;
+       sub->failover = subform->subfailover;
 
        /* Get conninfo */
        datum = SysCacheGetAttrNotNull(SUBSCRIPTIONOID,
index c62aa0074a314b6e2b91d33dde5a096c22b5953a..6791bff9dd2b6a5ccd9251399f20e5d2d3a2e4cc 100644 (file)
@@ -1358,7 +1358,7 @@ REVOKE ALL ON pg_replication_origin_status FROM public;
 REVOKE ALL ON pg_subscription FROM public;
 GRANT SELECT (oid, subdbid, subskiplsn, subname, subowner, subenabled,
               subbinary, substream, subtwophasestate, subdisableonerr,
-                         subpasswordrequired, subrunasowner,
+                         subpasswordrequired, subrunasowner, subfailover,
               subslotname, subsynccommit, subpublications, suborigin)
     ON pg_subscription TO public;
 
index eaf2ec3b362640544b9b52571f412db531198192..b647a81fc869dc10178f8d67b43f7c74f18b0d9f 100644 (file)
 #define SUBOPT_DISABLE_ON_ERR          0x00000400
 #define SUBOPT_PASSWORD_REQUIRED       0x00000800
 #define SUBOPT_RUN_AS_OWNER                    0x00001000
-#define SUBOPT_LSN                                     0x00002000
-#define SUBOPT_ORIGIN                          0x00004000
+#define SUBOPT_FAILOVER                                0x00002000
+#define SUBOPT_LSN                                     0x00004000
+#define SUBOPT_ORIGIN                          0x00008000
+
 
 /* check if the 'val' has 'bits' set */
 #define IsSet(val, bits)  (((val) & (bits)) == (bits))
@@ -95,6 +97,7 @@ typedef struct SubOpts
        bool            disableonerr;
        bool            passwordrequired;
        bool            runasowner;
+       bool            failover;
        char       *origin;
        XLogRecPtr      lsn;
 } SubOpts;
@@ -155,6 +158,8 @@ parse_subscription_options(ParseState *pstate, List *stmt_options,
                opts->passwordrequired = true;
        if (IsSet(supported_opts, SUBOPT_RUN_AS_OWNER))
                opts->runasowner = false;
+       if (IsSet(supported_opts, SUBOPT_FAILOVER))
+               opts->failover = false;
        if (IsSet(supported_opts, SUBOPT_ORIGIN))
                opts->origin = pstrdup(LOGICALREP_ORIGIN_ANY);
 
@@ -303,6 +308,15 @@ parse_subscription_options(ParseState *pstate, List *stmt_options,
                        opts->specified_opts |= SUBOPT_RUN_AS_OWNER;
                        opts->runasowner = defGetBoolean(defel);
                }
+               else if (IsSet(supported_opts, SUBOPT_FAILOVER) &&
+                                strcmp(defel->defname, "failover") == 0)
+               {
+                       if (IsSet(opts->specified_opts, SUBOPT_FAILOVER))
+                               errorConflictingDefElem(defel, pstate);
+
+                       opts->specified_opts |= SUBOPT_FAILOVER;
+                       opts->failover = defGetBoolean(defel);
+               }
                else if (IsSet(supported_opts, SUBOPT_ORIGIN) &&
                                 strcmp(defel->defname, "origin") == 0)
                {
@@ -388,6 +402,13 @@ parse_subscription_options(ParseState *pstate, List *stmt_options,
                                         errmsg("%s and %s are mutually exclusive options",
                                                        "connect = false", "copy_data = true")));
 
+               if (opts->failover &&
+                       IsSet(opts->specified_opts, SUBOPT_FAILOVER))
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_SYNTAX_ERROR),
+                                        errmsg("%s and %s are mutually exclusive options",
+                                                       "connect = false", "failover = true")));
+
                /* Change the defaults of other options. */
                opts->enabled = false;
                opts->create_slot = false;
@@ -591,7 +612,7 @@ CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt,
                                          SUBOPT_SYNCHRONOUS_COMMIT | SUBOPT_BINARY |
                                          SUBOPT_STREAMING | SUBOPT_TWOPHASE_COMMIT |
                                          SUBOPT_DISABLE_ON_ERR | SUBOPT_PASSWORD_REQUIRED |
-                                         SUBOPT_RUN_AS_OWNER | SUBOPT_ORIGIN);
+                                         SUBOPT_RUN_AS_OWNER | SUBOPT_FAILOVER | SUBOPT_ORIGIN);
        parse_subscription_options(pstate, stmt->options, supported_opts, &opts);
 
        /*
@@ -697,6 +718,7 @@ CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt,
        values[Anum_pg_subscription_subdisableonerr - 1] = BoolGetDatum(opts.disableonerr);
        values[Anum_pg_subscription_subpasswordrequired - 1] = BoolGetDatum(opts.passwordrequired);
        values[Anum_pg_subscription_subrunasowner - 1] = BoolGetDatum(opts.runasowner);
+       values[Anum_pg_subscription_subfailover - 1] = BoolGetDatum(opts.failover);
        values[Anum_pg_subscription_subconninfo - 1] =
                CStringGetTextDatum(conninfo);
        if (opts.slot_name)
@@ -807,7 +829,7 @@ CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt,
                                        twophase_enabled = true;
 
                                walrcv_create_slot(wrconn, opts.slot_name, false, twophase_enabled,
-                                                                  false, CRS_NOEXPORT_SNAPSHOT, NULL);
+                                                                  opts.failover, CRS_NOEXPORT_SNAPSHOT, NULL);
 
                                if (twophase_enabled)
                                        UpdateTwoPhaseState(subid, LOGICALREP_TWOPHASE_STATE_ENABLED);
@@ -816,6 +838,24 @@ CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt,
                                                (errmsg("created replication slot \"%s\" on publisher",
                                                                opts.slot_name)));
                        }
+
+                       /*
+                        * If the slot_name is specified without the create_slot option,
+                        * it is possible that the user intends to use an existing slot on
+                        * the publisher, so here we alter the failover property of the
+                        * slot to match the failover value in subscription.
+                        *
+                        * We do not need to change the failover to false if the server
+                        * does not support failover (e.g. pre-PG17).
+                        */
+                       else if (opts.slot_name &&
+                                        (opts.failover || walrcv_server_version(wrconn) >= 170000))
+                       {
+                               walrcv_alter_slot(wrconn, opts.slot_name, opts.failover);
+                               ereport(NOTICE,
+                                               (errmsg("changed the failover state of replication slot \"%s\" on publisher to %s",
+                                                               opts.slot_name, opts.failover ? "true" : "false")));
+                       }
                }
                PG_FINALLY();
                {
@@ -1132,7 +1172,8 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
                                                                  SUBOPT_SYNCHRONOUS_COMMIT | SUBOPT_BINARY |
                                                                  SUBOPT_STREAMING | SUBOPT_DISABLE_ON_ERR |
                                                                  SUBOPT_PASSWORD_REQUIRED |
-                                                                 SUBOPT_RUN_AS_OWNER | SUBOPT_ORIGIN);
+                                                                 SUBOPT_RUN_AS_OWNER | SUBOPT_FAILOVER |
+                                                                 SUBOPT_ORIGIN);
 
                                parse_subscription_options(pstate, stmt->options,
                                                                                   supported_opts, &opts);
@@ -1211,6 +1252,31 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
                                        replaces[Anum_pg_subscription_subrunasowner - 1] = true;
                                }
 
+                               if (IsSet(opts.specified_opts, SUBOPT_FAILOVER))
+                               {
+                                       if (!sub->slotname)
+                                               ereport(ERROR,
+                                                               (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+                                                                errmsg("cannot set %s for a subscription that does not have a slot name",
+                                                                               "failover")));
+
+                                       /*
+                                        * Do not allow changing the failover state if the
+                                        * subscription is enabled. This is because the failover
+                                        * state of the slot on the publisher cannot be modified
+                                        * if the slot is currently acquired by the apply worker.
+                                        */
+                                       if (sub->enabled)
+                                               ereport(ERROR,
+                                                               (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+                                                                errmsg("cannot set %s for enabled subscription",
+                                                                               "failover")));
+
+                                       values[Anum_pg_subscription_subfailover - 1] =
+                                               BoolGetDatum(opts.failover);
+                                       replaces[Anum_pg_subscription_subfailover - 1] = true;
+                               }
+
                                if (IsSet(opts.specified_opts, SUBOPT_ORIGIN))
                                {
                                        values[Anum_pg_subscription_suborigin - 1] =
@@ -1453,6 +1519,46 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
                heap_freetuple(tup);
        }
 
+       /*
+        * Try to acquire the connection necessary for altering slot.
+        *
+        * This has to be at the end because otherwise if there is an error while
+        * doing the database operations we won't be able to rollback altered
+        * slot.
+        */
+       if (replaces[Anum_pg_subscription_subfailover - 1])
+       {
+               bool            must_use_password;
+               char       *err;
+               WalReceiverConn *wrconn;
+
+               /* Load the library providing us libpq calls. */
+               load_file("libpqwalreceiver", false);
+
+               /* Try to connect to the publisher. */
+               must_use_password = sub->passwordrequired && !sub->ownersuperuser;
+               wrconn = walrcv_connect(sub->conninfo, true, must_use_password,
+                                                               sub->name, &err);
+               if (!wrconn)
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_CONNECTION_FAILURE),
+                                        errmsg("could not connect to the publisher: %s", err)));
+
+               PG_TRY();
+               {
+                       walrcv_alter_slot(wrconn, sub->slotname, opts.failover);
+
+                       ereport(NOTICE,
+                                       (errmsg("changed the failover state of replication slot \"%s\" on publisher to %s",
+                                                       sub->slotname, opts.failover ? "true" : "false")));
+               }
+               PG_FINALLY();
+               {
+                       walrcv_disconnect(wrconn);
+               }
+               PG_END_TRY();
+       }
+
        table_close(rel, RowExclusiveLock);
 
        ObjectAddressSet(myself, SubscriptionRelationId, subid);
index 4207b9356c55eb2047f174eeec656c971a996a28..5acab3f3e2306acbb323fb7c17be3ac44e8679f5 100644 (file)
@@ -1430,7 +1430,7 @@ LogicalRepSyncTableStart(XLogRecPtr *origin_startpos)
         */
        walrcv_create_slot(LogRepWorkerWalRcvConn,
                                           slotname, false /* permanent */ , false /* two_phase */ ,
-                                          false,
+                                          MySubscription->failover,
                                           CRS_USE_SNAPSHOT, origin_startpos);
 
        /*
index 9b598caf3caded024f54b4a83fedcecd4481cbc0..32ff4c033643231daeff4fb8f0686e96283c9939 100644 (file)
  * avoid such deadlocks, we generate a unique GID (consisting of the
  * subscription oid and the xid of the prepared transaction) for each prepare
  * transaction on the subscriber.
+ *
+ * FAILOVER
+ * ----------------------
+ * The logical slot on the primary can be synced to the standby by specifying
+ * failover = true when creating the subscription. Enabling failover allows us
+ * to smoothly transition to the promoted standby, ensuring that we can
+ * subscribe to the new primary without losing any data.
  *-------------------------------------------------------------------------
  */
 
index a19443becd69061c2d7f39fb0c3226bdc3f76a35..348748bae538fad615cd5ffde132b08bc94acbd4 100644 (file)
@@ -4641,6 +4641,7 @@ getSubscriptions(Archive *fout)
        int                     i_suborigin;
        int                     i_suboriginremotelsn;
        int                     i_subenabled;
+       int                     i_subfailover;
        int                     i,
                                ntups;
 
@@ -4706,10 +4707,12 @@ getSubscriptions(Archive *fout)
 
        if (dopt->binary_upgrade && fout->remoteVersion >= 170000)
                appendPQExpBufferStr(query, " o.remote_lsn AS suboriginremotelsn,\n"
-                                                        " s.subenabled\n");
+                                                        " s.subenabled,\n"
+                                                        " s.subfailover\n");
        else
                appendPQExpBufferStr(query, " NULL AS suboriginremotelsn,\n"
-                                                        " false AS subenabled\n");
+                                                        " false AS subenabled,\n"
+                                                        " false AS subfailover\n");
 
        appendPQExpBufferStr(query,
                                                 "FROM pg_subscription s\n");
@@ -4748,6 +4751,7 @@ getSubscriptions(Archive *fout)
        i_suborigin = PQfnumber(res, "suborigin");
        i_suboriginremotelsn = PQfnumber(res, "suboriginremotelsn");
        i_subenabled = PQfnumber(res, "subenabled");
+       i_subfailover = PQfnumber(res, "subfailover");
 
        subinfo = pg_malloc(ntups * sizeof(SubscriptionInfo));
 
@@ -4792,6 +4796,8 @@ getSubscriptions(Archive *fout)
                                pg_strdup(PQgetvalue(res, i, i_suboriginremotelsn));
                subinfo[i].subenabled =
                        pg_strdup(PQgetvalue(res, i, i_subenabled));
+               subinfo[i].subfailover =
+                       pg_strdup(PQgetvalue(res, i, i_subfailover));
 
                /* Decide whether we want to dump it */
                selectDumpableObject(&(subinfo[i].dobj), fout);
@@ -5062,6 +5068,17 @@ dumpSubscription(Archive *fout, const SubscriptionInfo *subinfo)
                        appendPQExpBuffer(query, ", '%s');\n", subinfo->suboriginremotelsn);
                }
 
+               if (strcmp(subinfo->subfailover, "t") == 0)
+               {
+                       /*
+                        * Enable the failover to allow the subscription's slot to be
+                        * synced to the standbys after the upgrade.
+                        */
+                       appendPQExpBufferStr(query,
+                                                                "\n-- For binary upgrade, must preserve the subscriber's failover option.\n");
+                       appendPQExpBuffer(query, "ALTER SUBSCRIPTION %s SET(failover = true);\n", qsubname);
+               }
+
                if (strcmp(subinfo->subenabled, "t") == 0)
                {
                        /*
index 93d97a40900152982a4b31dce78f073c0080868e..77db42e354b198068514f6161cbec1123919a89d 100644 (file)
@@ -667,6 +667,7 @@ typedef struct _SubscriptionInfo
        char       *subpublications;
        char       *suborigin;
        char       *suboriginremotelsn;
+       char       *subfailover;
 } SubscriptionInfo;
 
 /*
index 0ab368247b08042bfb2522365ab16fb23480dcd0..83d71c3084bac825c85187638d5b17ce8877435f 100644 (file)
@@ -172,7 +172,7 @@ $sub->start;
 $sub->safe_psql(
        'postgres', qq[
        CREATE TABLE tbl (a int);
-       CREATE SUBSCRIPTION regress_sub CONNECTION '$old_connstr' PUBLICATION regress_pub WITH (two_phase = 'true')
+       CREATE SUBSCRIPTION regress_sub CONNECTION '$old_connstr' PUBLICATION regress_pub WITH (two_phase = 'true', failover = 'true')
 ]);
 $sub->wait_for_subscription_sync($oldpub, 'regress_sub');
 
@@ -192,8 +192,8 @@ command_ok([@pg_upgrade_cmd], 'run of pg_upgrade of old cluster');
 # Check that the slot 'regress_sub' has migrated to the new cluster
 $newpub->start;
 my $result = $newpub->safe_psql('postgres',
-       "SELECT slot_name, two_phase FROM pg_replication_slots");
-is($result, qq(regress_sub|t), 'check the slot exists on new cluster');
+       "SELECT slot_name, two_phase, failover FROM pg_replication_slots");
+is($result, qq(regress_sub|t|t), 'check the slot exists on new cluster');
 
 # Update the connection
 my $new_connstr = $newpub->connstr . ' dbname=postgres';
index 9e8f9b797063ce46256cdc2040434ae51cf22d4d..792f221695a667ccdba663f786640979488bcb8a 100644 (file)
@@ -49,11 +49,11 @@ $old_sub->safe_psql(
 # Setup logical replication
 my $connstr = $publisher->connstr . ' dbname=postgres';
 
-# Setup an enabled subscription to verify that the running status is retained
-# after upgrade.
+# Setup an enabled subscription to verify that the running status and failover
+# option are retained after the upgrade.
 $publisher->safe_psql('postgres', "CREATE PUBLICATION regress_pub1");
 $old_sub->safe_psql('postgres',
-       "CREATE SUBSCRIPTION regress_sub1 CONNECTION '$connstr' PUBLICATION regress_pub1"
+       "CREATE SUBSCRIPTION regress_sub1 CONNECTION '$connstr' PUBLICATION regress_pub1 WITH (failover = true)"
 );
 $old_sub->wait_for_subscription_sync($publisher, 'regress_sub1');
 
@@ -137,14 +137,14 @@ $publisher->safe_psql(
 
 $new_sub->start;
 
-# The subscription's running status should be preserved. Old subscription
-# regress_sub1 should be enabled and old subscription regress_sub2 should be
-# disabled.
+# The subscription's running status and failover option should be preserved.
+# Old subscription regress_sub1 should have enabled and failover as true while
+# old subscription regress_sub2 should have enabled and failover as false.
 $result =
   $new_sub->safe_psql('postgres',
-       "SELECT subname, subenabled FROM pg_subscription ORDER BY subname");
-is( $result, qq(regress_sub1|t
-regress_sub2|f),
+       "SELECT subname, subenabled, subfailover FROM pg_subscription ORDER BY subname");
+is( $result, qq(regress_sub1|t|t
+regress_sub2|f|f),
        "check that the subscription's running status are preserved");
 
 my $sub_oid = $new_sub->safe_psql('postgres',
index 9cd8783325c435cb240904819b32ff796c4e5842..b6a4eb1d56546aa178e9c5781d75a2a0c6a87e29 100644 (file)
@@ -6571,7 +6571,8 @@ describeSubscriptions(const char *pattern, bool verbose)
        PGresult   *res;
        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, false, false,
+       false};
 
        if (pset.sversion < 100000)
        {
@@ -6635,6 +6636,11 @@ describeSubscriptions(const char *pattern, bool verbose)
                                                          gettext_noop("Password required"),
                                                          gettext_noop("Run as owner?"));
 
+               if (pset.sversion >= 170000)
+                       appendPQExpBuffer(&buf,
+                                                         ", subfailover AS \"%s\"\n",
+                                                         gettext_noop("Failover"));
+
                appendPQExpBuffer(&buf,
                                                  ",  subsynccommit AS \"%s\"\n"
                                                  ",  subconninfo AS \"%s\"\n",
index ada711d02ff297ff274cd1b047f63ff8306212d3..151a5211ee4a20a4a90cd806fe0ef40ea0e810af 100644 (file)
@@ -1943,7 +1943,7 @@ psql_completion(const char *text, int start, int end)
                COMPLETE_WITH("(", "PUBLICATION");
        /* ALTER SUBSCRIPTION <name> SET ( */
        else if (HeadMatches("ALTER", "SUBSCRIPTION", MatchAny) && TailMatches("SET", "("))
-               COMPLETE_WITH("binary", "disable_on_error", "origin",
+               COMPLETE_WITH("binary", "disable_on_error", "failover", "origin",
                                          "password_required", "run_as_owner", "slot_name",
                                          "streaming", "synchronous_commit");
        /* ALTER SUBSCRIPTION <name> SKIP ( */
@@ -3340,7 +3340,7 @@ psql_completion(const char *text, int start, int end)
        /* Complete "CREATE SUBSCRIPTION <name> ...  WITH ( <opt>" */
        else if (HeadMatches("CREATE", "SUBSCRIPTION") && TailMatches("WITH", "("))
                COMPLETE_WITH("binary", "connect", "copy_data", "create_slot",
-                                         "disable_on_error", "enabled", "origin",
+                                         "disable_on_error", "enabled", "failover", "origin",
                                          "password_required", "run_as_owner", "slot_name",
                                          "streaming", "synchronous_commit", "two_phase");
 
index 739f9253bfe8a191fd1d1bc66a97865b48cf0a89..9fc8ac92905532125a3b5bf317219ef3d890242c 100644 (file)
@@ -57,6 +57,6 @@
  */
 
 /*                                                     yyyymmddN */
-#define CATALOG_VERSION_NO     202401252
+#define CATALOG_VERSION_NO     202401301
 
 #endif
index ab206bad7ded524e99634c60917c3aef227c8862..0aa14ec4a27ed168180648b5bc6874bfd49118db 100644 (file)
@@ -93,6 +93,11 @@ CATALOG(pg_subscription,6100,SubscriptionRelationId) BKI_SHARED_RELATION BKI_ROW
        bool            subrunasowner;  /* True if replication should execute as the
                                                                 * subscription owner */
 
+       bool            subfailover;    /* True if the associated replication slots
+                                                                * (i.e. the main slot and the table sync
+                                                                * slots) in the upstream database are enabled
+                                                                * to be synchronized to the standbys. */
+
 #ifdef CATALOG_VARLEN                  /* variable-length fields start here */
        /* Connection string to the publisher */
        text            subconninfo BKI_FORCE_NOT_NULL;
@@ -142,6 +147,10 @@ typedef struct Subscription
                                                                 * occurs */
        bool            passwordrequired;       /* Must connection use a password? */
        bool            runasowner;             /* Run replication as subscription owner */
+       bool            failover;               /* True if the associated replication slots
+                                                                * (i.e. the main slot and the table sync
+                                                                * slots) in the upstream database are enabled
+                                                                * to be synchronized to the standbys. */
        char       *conninfo;           /* Connection string to the publisher */
        char       *slotname;           /* Name of the replication slot */
        char       *synccommit;         /* Synchronous commit setting for worker */
index 88fb0306f546328d824baea7f2d18f831a5c4562..bf087ac2a9eebbfd1b8209ce12d986670a8f04e1 100644 (file)
@@ -45,6 +45,7 @@ tests += {
       't/037_invalid_database.pl',
       't/038_save_logical_slots_shutdown.pl',
       't/039_end_of_wal.pl',
+      't/040_standby_failover_slots_sync.pl',
     ],
   },
 }
diff --git a/src/test/recovery/t/040_standby_failover_slots_sync.pl b/src/test/recovery/t/040_standby_failover_slots_sync.pl
new file mode 100644 (file)
index 0000000..bc58ff4
--- /dev/null
@@ -0,0 +1,100 @@
+
+# Copyright (c) 2024, PostgreSQL Global Development Group
+
+use strict;
+use warnings;
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+##################################################
+# Test that when a subscription with failover enabled is created, it will alter
+# the failover property of the corresponding slot on the publisher.
+##################################################
+
+# Create publisher
+my $publisher = PostgreSQL::Test::Cluster->new('publisher');
+$publisher->init(allows_streaming => 'logical');
+$publisher->start;
+
+$publisher->safe_psql('postgres',
+       "CREATE PUBLICATION regress_mypub FOR ALL TABLES;");
+
+my $publisher_connstr = $publisher->connstr . ' dbname=postgres';
+
+# Create a subscriber node, wait for sync to complete
+my $subscriber1 = PostgreSQL::Test::Cluster->new('subscriber1');
+$subscriber1->init;
+$subscriber1->start;
+
+# Create a slot on the publisher with failover disabled
+$publisher->safe_psql('postgres',
+       "SELECT 'init' FROM pg_create_logical_replication_slot('lsub1_slot', 'pgoutput', false, false, false);"
+);
+
+# Confirm that the failover flag on the slot is turned off
+is( $publisher->safe_psql(
+               'postgres',
+               q{SELECT failover from pg_replication_slots WHERE slot_name = 'lsub1_slot';}
+       ),
+       "f",
+       'logical slot has failover false on the publisher');
+
+# Create a subscription (using the same slot created above) that enables
+# failover.
+$subscriber1->safe_psql('postgres',
+       "CREATE SUBSCRIPTION regress_mysub1 CONNECTION '$publisher_connstr' PUBLICATION regress_mypub WITH (slot_name = lsub1_slot, copy_data=false, failover = true, create_slot = false, enabled = false);"
+);
+
+# Confirm that the failover flag on the slot has now been turned on
+is( $publisher->safe_psql(
+               'postgres',
+               q{SELECT failover from pg_replication_slots WHERE slot_name = 'lsub1_slot';}
+       ),
+       "t",
+       'logical slot has failover true on the publisher');
+
+##################################################
+# Test that changing the failover property of a subscription updates the
+# corresponding failover property of the slot.
+##################################################
+
+# Disable failover
+$subscriber1->safe_psql('postgres',
+       "ALTER SUBSCRIPTION regress_mysub1 SET (failover = false)");
+
+# Confirm that the failover flag on the slot has now been turned off
+is( $publisher->safe_psql(
+               'postgres',
+               q{SELECT failover from pg_replication_slots WHERE slot_name = 'lsub1_slot';}
+       ),
+       "f",
+       'logical slot has failover false on the publisher');
+
+# Enable failover
+$subscriber1->safe_psql('postgres',
+       "ALTER SUBSCRIPTION regress_mysub1 SET (failover = true)");
+
+# Confirm that the failover flag on the slot has now been turned on
+is( $publisher->safe_psql(
+               'postgres',
+               q{SELECT failover from pg_replication_slots WHERE slot_name = 'lsub1_slot';}
+       ),
+       "t",
+       'logical slot has failover true on the publisher');
+
+##################################################
+# Test that the failover option cannot be changed for enabled subscriptions.
+##################################################
+
+# Enable subscription
+$subscriber1->safe_psql('postgres',
+       "ALTER SUBSCRIPTION regress_mysub1 ENABLE");
+
+# Disable failover for enabled subscription
+my ($result, $stdout, $stderr) = $subscriber1->psql('postgres',
+       "ALTER SUBSCRIPTION regress_mysub1 SET (failover = false)");
+ok( $stderr =~ /ERROR:  cannot set failover for enabled subscription/,
+       "altering failover is not allowed for enabled subscription");
+
+done_testing();
index b15eddbff3c86f24f38f66fba815c6810b70fb3c..1eee6b17b8fa43f03d96050a35d70d828e7f9c60 100644 (file)
@@ -89,6 +89,8 @@ CREATE SUBSCRIPTION regress_testsub2 CONNECTION 'dbname=regress_doesnotexist' PU
 ERROR:  connect = false and enabled = true are mutually exclusive options
 CREATE SUBSCRIPTION regress_testsub2 CONNECTION 'dbname=regress_doesnotexist' PUBLICATION testpub WITH (connect = false, create_slot = true);
 ERROR:  connect = false and create_slot = true are mutually exclusive options
+CREATE SUBSCRIPTION regress_testsub2 CONNECTION 'dbname=regress_doesnotexist' PUBLICATION testpub WITH (connect = false, failover = true);
+ERROR:  connect = false and failover = true are mutually exclusive options
 CREATE SUBSCRIPTION regress_testsub2 CONNECTION 'dbname=regress_doesnotexist' PUBLICATION testpub WITH (slot_name = NONE, enabled = true);
 ERROR:  slot_name = NONE and enabled = true are mutually exclusive options
 CREATE SUBSCRIPTION regress_testsub2 CONNECTION 'dbname=regress_doesnotexist' PUBLICATION testpub WITH (slot_name = NONE, enabled = false, create_slot = true);
@@ -116,18 +118,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? | Synchronous commit |          Conninfo           | Skip LSN 
-------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub4 | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | none   | t                 | 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 
+------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub4 | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | none   | t                 | f             | f        | off                | dbname=regress_doesnotexist | 0/0
 (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? | Synchronous commit |          Conninfo           | Skip LSN 
-------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub4 | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | 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 
+------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub4 | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | f        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 DROP SUBSCRIPTION regress_testsub3;
@@ -145,10 +147,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? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | 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 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | f        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET PUBLICATION testpub2, testpub3 WITH (refresh = false);
@@ -157,10 +159,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? | Synchronous commit |           Conninfo           | Skip LSN 
------------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+------------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | f                 | t             | 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 
+-----------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+------------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | f                 | t             | f        | off                | dbname=regress_doesnotexist2 | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (password_required = true);
@@ -176,10 +178,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? | Synchronous commit |           Conninfo           | Skip LSN 
------------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+------------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | t                 | 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 
+-----------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+------------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | t                 | f             | f        | off                | dbname=regress_doesnotexist2 | 0/12345
 (1 row)
 
 -- ok - with lsn = NONE
@@ -188,10 +190,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? | Synchronous commit |           Conninfo           | Skip LSN 
------------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+------------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | 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 
+-----------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+------------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | t                 | f             | f        | off                | dbname=regress_doesnotexist2 | 0/0
 (1 row)
 
 BEGIN;
@@ -223,10 +225,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? | Synchronous commit |           Conninfo           | Skip LSN 
----------------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+------------------------------+----------
- regress_testsub_foo | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | t                 | 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 
+---------------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+------------------------------+----------
+ regress_testsub_foo | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | t                 | f             | f        | local              | dbname=regress_doesnotexist2 | 0/0
 (1 row)
 
 -- rename back to keep the rest simple
@@ -255,19 +257,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? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | t      | off       | d                | f                | any    | t                 | 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 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | t      | off       | d                | f                | any    | t                 | f             | f        | off                | dbname=regress_doesnotexist | 0/0
 (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? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | 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 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | f        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 DROP SUBSCRIPTION regress_testsub;
@@ -279,27 +281,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? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | on        | d                | f                | any    | t                 | 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 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | on        | d                | f                | any    | t                 | f             | f        | off                | dbname=regress_doesnotexist | 0/0
 (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? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | parallel  | d                | f                | any    | t                 | 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 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | parallel  | d                | f                | any    | t                 | f             | f        | off                | dbname=regress_doesnotexist | 0/0
 (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? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | 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 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | f        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 -- fail - publication already exists
@@ -314,10 +316,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? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-----------------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub,testpub1,testpub2} | f      | off       | d                | f                | any    | t                 | 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 
+-----------------+---------------------------+---------+-----------------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub,testpub1,testpub2} | f      | off       | d                | f                | any    | t                 | f             | f        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 -- fail - publication used more than once
@@ -332,10 +334,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? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | 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 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | f        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 DROP SUBSCRIPTION regress_testsub;
@@ -371,10 +373,10 @@ 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? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | p                | f                | any    | t                 | 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 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | p                | f                | any    | t                 | f             | f        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 --fail - alter of two_phase option not supported.
@@ -383,10 +385,10 @@ ERROR:  unrecognized subscription parameter: "two_phase"
 -- but 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? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | on        | p                | f                | any    | t                 | 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 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | on        | p                | f                | any    | t                 | f             | f        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
@@ -396,10 +398,10 @@ 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? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | on        | p                | f                | any    | t                 | 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 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | on        | p                | f                | any    | t                 | f             | f        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
@@ -412,18 +414,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? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | 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 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | f        | off                | dbname=regress_doesnotexist | 0/0
 (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? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | t                | any    | t                 | 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 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | t                | any    | t                 | f             | f        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
index 444e563ff3f43222b5a552e33d0b22fac1e994e3..1b2a23ba7bf455081ee621216b05c81f1c893eb2 100644 (file)
@@ -54,6 +54,7 @@ SET SESSION AUTHORIZATION 'regress_subscription_user';
 CREATE SUBSCRIPTION regress_testsub2 CONNECTION 'dbname=regress_doesnotexist' PUBLICATION testpub WITH (connect = false, copy_data = true);
 CREATE SUBSCRIPTION regress_testsub2 CONNECTION 'dbname=regress_doesnotexist' PUBLICATION testpub WITH (connect = false, enabled = true);
 CREATE SUBSCRIPTION regress_testsub2 CONNECTION 'dbname=regress_doesnotexist' PUBLICATION testpub WITH (connect = false, create_slot = true);
+CREATE SUBSCRIPTION regress_testsub2 CONNECTION 'dbname=regress_doesnotexist' PUBLICATION testpub WITH (connect = false, failover = true);
 CREATE SUBSCRIPTION regress_testsub2 CONNECTION 'dbname=regress_doesnotexist' PUBLICATION testpub WITH (slot_name = NONE, enabled = true);
 CREATE SUBSCRIPTION regress_testsub2 CONNECTION 'dbname=regress_doesnotexist' PUBLICATION testpub WITH (slot_name = NONE, enabled = false, create_slot = true);
 CREATE SUBSCRIPTION regress_testsub2 CONNECTION 'dbname=regress_doesnotexist' PUBLICATION testpub WITH (slot_name = NONE);