From 9f957fa1556725f7a86e0a4f61b52b3f1bbfa571 Mon Sep 17 00:00:00 2001
From: Greg Nancarrow <gregn4422@gmail.com>
Date: Sun, 19 Jul 2020 21:45:15 +1000
Subject: [PATCH v17] Enhance libpq target_session_attrs and add
 target_server_type.

Enhance the connection parameter "target_session_attrs" to support values
primary/standby/prefer-standby (using the existing "read-write" as a synonym
for "primary"). To provide closer alignment with similar functionality in the
PGJDBC driver, add a new connection parameter "target_server_type".

Add "in_recovery" as a GUC_REPORT variable, to update clients when the server
is in recovery mode. This improves the speed of client connections to a standby
server, by avoiding the need to execute a command to determine if the server is
in recovery mode.

Add new SIGUSR1 handling interrupt to support reporting of recovery mode exit
to all backends and their respective clients.

Some parts of the code are taken from earlier development by
Elvis Pranskevichus and Tsunakawa Takayuki.

Discussion: https://www.postgresql.org/message-id/flat/CAF3+xM+8-ztOkaV9gHiJ3wfgENTq97QcjXQt+rbFQ6F7oNzt9A@mail.gmail.com
---
 contrib/postgres_fdw/expected/postgres_fdw.out |   2 +-
 doc/src/sgml/high-availability.sgml            |   5 +-
 doc/src/sgml/libpq.sgml                        | 111 +++++-
 doc/src/sgml/protocol.sgml                     |   8 +-
 src/backend/access/transam/xlog.c              |   3 +
 src/backend/storage/ipc/procarray.c            |  28 ++
 src/backend/storage/ipc/procsignal.c           |   3 +
 src/backend/storage/ipc/standby.c              |   9 +
 src/backend/tcop/postgres.c                    |  59 ++++
 src/backend/utils/init/postinit.c              |   9 +-
 src/backend/utils/misc/check_guc               |   2 +-
 src/backend/utils/misc/guc.c                   |  16 +
 src/include/storage/procarray.h                |   1 +
 src/include/storage/procsignal.h               |   2 +
 src/include/storage/standby.h                  |   1 +
 src/include/tcop/tcopprot.h                    |   2 +
 src/interfaces/libpq/fe-connect.c              | 445 ++++++++++++++++++++++---
 src/interfaces/libpq/fe-exec.c                 |   6 +-
 src/interfaces/libpq/libpq-fe.h                |   3 +-
 src/interfaces/libpq/libpq-int.h               |  48 ++-
 src/test/recovery/t/001_stream_rep.pl          | 146 +++++++-
 21 files changed, 830 insertions(+), 79 deletions(-)

diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index 90db550..ba0ba6e 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -8898,7 +8898,7 @@ DO $d$
     END;
 $d$;
 ERROR:  invalid option "password"
-HINT:  Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size
+HINT:  Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, target_server_type, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size
 CONTEXT:  SQL statement "ALTER SERVER loopback_nopw OPTIONS (ADD password 'dummypw')"
 PL/pgSQL function inline_code_block line 3 at EXECUTE
 -- If we add a password for our user mapping instead, we should get a different
diff --git a/doc/src/sgml/high-availability.sgml b/doc/src/sgml/high-availability.sgml
index 6a9184f..e2b8cfa 100644
--- a/doc/src/sgml/high-availability.sgml
+++ b/doc/src/sgml/high-availability.sgml
@@ -1886,8 +1886,9 @@ if (!triggered)
    </para>
 
    <para>
-    During hot standby, the parameter <varname>transaction_read_only</varname> is always
-    true and may not be changed.  But as long as no attempt is made to modify
+    During hot standby, the parameters <varname>in_recovery</varname> and
+    <varname>transaction_read_only</varname> are always true and may not be
+    changed. But as long as no attempt is made to modify
     the database, connections during hot standby will act much like any other
     database connection.  If failover or switchover occurs, the database will
     switch to normal processing mode.  Sessions will remain connected while the
diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index f7b765f..26673c9 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -1811,18 +1811,89 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
       <term><literal>target_session_attrs</literal></term>
       <listitem>
        <para>
-        If this parameter is set to <literal>read-write</literal>, only a
-        connection in which read-write transactions are accepted by default
-        is considered acceptable.  The query
-        <literal>SHOW transaction_read_only</literal> will be sent upon any
-        successful connection; if it returns <literal>on</literal>, the connection
-        will be closed.  If multiple hosts were specified in the connection
-        string, any remaining servers will be tried just as if the connection
-        attempt had failed.  The default value of this parameter,
-        <literal>any</literal>, regards all connections as acceptable.
-      </para>
+        The supported options for this parameter are <literal>any</literal>,
+        <literal>primary</literal>, <literal>standby</literal> and
+        <literal>prefer-standby</literal>.
+        <literal>primary</literal> may alternatively be specified as <literal>read-write</literal>.
+        <literal>standby</literal> may alternatively be specified as <literal>secondary</literal>.
+        <literal>prefer-standby</literal> may alternatively be specified as
+        <literal>prefer-secondary</literal>.
+        The default value of this parameter, <literal>any</literal>, regards
+        all connections as acceptable. If multiple hosts are specified in the
+        connection string, each host is tried in the order given until a connection
+        is successful.
+       </para>
+
+       <para>
+        If this parameter is set to <literal>primary</literal>, then if the server is version 14
+        or greater, only a connection in which the server is not in recovery mode is considered
+        acceptable. The recovery mode state is determined by the value of the
+        <varname>in_recovery</varname> configuration parameter that is reported by the server upon
+        successful connection. Otherwise, if the server is prior to version 14, only a connection in
+        which read-write transactions are accepted by default is considered acceptable. To determine
+        whether the server supports read-write transactions, the query
+        <literal>SHOW transaction_read_only</literal> will be sent upon any successful connection; if
+        it returns <literal>on</literal>, it means the server doesn't support read-write transactions.
+       </para>
+
+       <para>
+        If this parameter is set to <literal>standby</literal>, only a connection in which
+        the server is in recovery mode is considered acceptable. If the server is prior to version 14,
+        the query <literal>SELECT pg_is_in_recovery()</literal> will be sent upon any successful
+        connection; if it returns <literal>t</literal>, it means the server is in recovery mode.
+       </para>
+
+       <para>
+        If this parameter is set to <literal>prefer-standby</literal>, a connection in which
+        the server is in recovery mode is preferred. If no such connections can be found,
+        then a connection in which the server is not in recovery mode will be considered.
+       </para>
+
       </listitem>
-    </varlistentry>
+     </varlistentry>
+
+     <varlistentry id="libpq-connect-target-server-type" xreflabel="target_server_type">
+      <term><literal>target_server_type</literal></term>
+      <listitem>
+       <para>
+        The supported options for this parameter are <literal>primary</literal>,
+        <literal>standby</literal> and <literal>prefer-standby</literal>.
+        <literal>primary</literal> may alternatively be specified as <literal>read-write</literal>.
+        <literal>standby</literal> may alternatively be specified as <literal>secondary</literal>.
+        <literal>prefer-standby</literal> may alternatively be specified as
+        <literal>prefer-secondary</literal>.
+        This parameter overrides any connection type specified by <literal>target_session_attrs</literal>.
+        If multiple hosts are specified in the connection string, each host is tried in the order given
+        until a connection is successful.
+       </para>
+
+       <para>
+        If this parameter is set to <literal>primary</literal>, then if the server is version 14
+        or greater, only a connection in which the server is not in recovery mode is considered
+        acceptable. The recovery mode state is determined by the value of the
+        <varname>in_recovery</varname> configuration parameter that is reported by the server upon
+        successful connection. Otherwise, if the server is prior to version 14, only a connection in
+        which read-write transactions are accepted by default is considered acceptable. To determine
+        whether the server supports read-write transactions, the query
+        <literal>SHOW transaction_read_only</literal> will be sent upon any successful connection; if
+        it returns <literal>on</literal>, it means the server doesn't support read-write transactions.
+       </para>
+
+       <para>
+        If this parameter is set to <literal>standby</literal>, only a connection in which
+        the server is in recovery mode is considered acceptable. If the server is prior to version 14,
+        the query <literal>SELECT pg_is_in_recovery()</literal> will be sent upon any successful
+        connection; if it returns <literal>t</literal>, it means the server is in recovery mode.
+       </para>
+
+       <para>
+        If this parameter is set to <literal>prefer-standby</literal>, a connection in which
+        the server is in recovery mode is preferred. If no such connections can be found,
+        then a connection in which the server is not in recovery mode will be considered.
+       </para>
+
+      </listitem>
+     </varlistentry>
     </variablelist>
    </para>
   </sect2>
@@ -2130,14 +2201,16 @@ const char *PQparameterStatus(const PGconn *conn, const char *paramName);
        <varname>DateStyle</varname>,
        <varname>IntervalStyle</varname>,
        <varname>TimeZone</varname>,
-       <varname>integer_datetimes</varname>, and
-       <varname>standard_conforming_strings</varname>.
+       <varname>integer_datetimes</varname>,
+       <varname>standard_conforming_strings</varname> and
+       <varname>in_recovery</varname>.
        (<varname>server_encoding</varname>, <varname>TimeZone</varname>, and
        <varname>integer_datetimes</varname> were not reported by releases before 8.0;
        <varname>standard_conforming_strings</varname> was not reported by releases
        before 8.1;
        <varname>IntervalStyle</varname> was not reported by releases before 8.4;
-       <varname>application_name</varname> was not reported by releases before 9.0.)
+       <varname>application_name</varname> was not reported by releases before 9.0;
+       <varname>in_recovery</varname> was not reported by releases before 14.0.)
        Note that
        <varname>server_version</varname>,
        <varname>server_encoding</varname> and
@@ -7237,6 +7310,16 @@ myEventProc(PGEventId evtId, void *evtInfo, void *passThrough)
       linkend="libpq-connect-target-session-attrs"/> connection parameter.
      </para>
     </listitem>
+
+    <listitem>
+     <para>
+      <indexterm>
+       <primary><envar>PGTARGETSERVERTYPE</envar></primary>
+      </indexterm>
+      <envar>PGTARGETSERVERTYPE</envar> behaves the same as the <xref
+      linkend="libpq-connect-target-server-type"/> connection parameter.
+     </para>
+    </listitem>
    </itemizedlist>
   </para>
 
diff --git a/doc/src/sgml/protocol.sgml b/doc/src/sgml/protocol.sgml
index 8b00235..36ac534 100644
--- a/doc/src/sgml/protocol.sgml
+++ b/doc/src/sgml/protocol.sgml
@@ -1283,14 +1283,16 @@ SELCT 1/0;<!-- this typo is intentional -->
     <varname>DateStyle</varname>,
     <varname>IntervalStyle</varname>,
     <varname>TimeZone</varname>,
-    <varname>integer_datetimes</varname>, and
-    <varname>standard_conforming_strings</varname>.
+    <varname>integer_datetimes</varname>,
+    <varname>standard_conforming_strings</varname> and
+    <varname>in_recovery</varname>.
     (<varname>server_encoding</varname>, <varname>TimeZone</varname>, and
     <varname>integer_datetimes</varname> were not reported by releases before 8.0;
     <varname>standard_conforming_strings</varname> was not reported by releases
     before 8.1;
     <varname>IntervalStyle</varname> was not reported by releases before 8.4;
-    <varname>application_name</varname> was not reported by releases before 9.0.)
+    <varname>application_name</varname> was not reported by releases before 9.0;
+    <varname>in_recovery</varname> was not reported by releases before 14.0.)
     Note that
     <varname>server_version</varname>,
     <varname>server_encoding</varname> and
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 0a97b1d..f1c3e9d 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -7943,6 +7943,9 @@ StartupXLOG(void)
 	XLogCtl->SharedRecoveryState = RECOVERY_STATE_DONE;
 	SpinLockRelease(&XLogCtl->info_lck);
 
+	if (standbyState != STANDBY_DISABLED)
+		SendRecoveryExitSignal();
+
 	UpdateControlFile();
 	LWLockRelease(ControlFileLock);
 
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index b448533..3917e24 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -3082,6 +3082,34 @@ TerminateOtherDBBackends(Oid databaseId)
 }
 
 /*
+ * SendSignalToAllBackends --- send a signal to all backends.
+ */
+void
+SendSignalToAllBackends(ProcSignalReason reason)
+{
+	ProcArrayStruct *arrayP = procArray;
+	int			index;
+	pid_t		pid = 0;
+
+	LWLockAcquire(ProcArrayLock, LW_SHARED);
+
+	for (index = 0; index < arrayP->numProcs; index++)
+	{
+		int			pgprocno = arrayP->pgprocnos[index];
+		volatile PGPROC *proc = &allProcs[pgprocno];
+		VirtualTransactionId procvxid;
+
+		GET_VXID_FROM_PGPROC(procvxid, *proc);
+
+		pid = proc->pid;
+		if (pid != 0)
+			(void) SendProcSignal(pid, reason, procvxid.backendId);
+	}
+
+	LWLockRelease(ProcArrayLock);
+}
+
+/*
  * ProcArraySetReplicationSlotXmin
  *
  * Install limits to future computations of the xmin horizon to prevent vacuum
diff --git a/src/backend/storage/ipc/procsignal.c b/src/backend/storage/ipc/procsignal.c
index 4fa385b..957df0d 100644
--- a/src/backend/storage/ipc/procsignal.c
+++ b/src/backend/storage/ipc/procsignal.c
@@ -585,6 +585,9 @@ procsignal_sigusr1_handler(SIGNAL_ARGS)
 	if (CheckProcSignal(PROCSIG_RECOVERY_CONFLICT_BUFFERPIN))
 		RecoveryConflictInterrupt(PROCSIG_RECOVERY_CONFLICT_BUFFERPIN);
 
+	if (CheckProcSignal(PROCSIG_RECOVERY_EXIT))
+		HandleRecoveryExitInterrupt();
+
 	SetLatch(MyLatch);
 
 	latch_sigusr1_handler();
diff --git a/src/backend/storage/ipc/standby.c b/src/backend/storage/ipc/standby.c
index f522983..aa8841b 100644
--- a/src/backend/storage/ipc/standby.c
+++ b/src/backend/storage/ipc/standby.c
@@ -140,6 +140,15 @@ ShutdownRecoveryTransactionEnvironment(void)
 	VirtualXactLockTableCleanup();
 }
 
+/*
+ * SendRecoveryExitSignal
+ *		Signal backends that the server has exited recovery mode.
+ */
+void
+SendRecoveryExitSignal(void)
+{
+	SendSignalToAllBackends(PROCSIG_RECOVERY_EXIT);
+}
 
 /*
  * -----------------------------------------------------
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index c9424f1..6e0481e 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -162,6 +162,14 @@ static bool UseSemiNewlineNewline = false;	/* -j switch */
 static bool RecoveryConflictPending = false;
 static bool RecoveryConflictRetryable = true;
 static ProcSignalReason RecoveryConflictReason;
+/*
+ * Inbound recovery exit are initially processed by
+ * HandleRecoveryExitInterrupt(), called from inside a signal handler.
+ * That just sets the recoveryExitInterruptPending flag and sets the process
+ * latch. ProcessRecoveryExitInterrupt() will then be called whenever it's
+ * safe to actually deal with the interrupt.
+ */
+volatile sig_atomic_t recoveryExitInterruptPending = false;
 
 /* reused buffer to pass to SendRowDescriptionMessage() */
 static MemoryContext row_description_context = NULL;
@@ -191,6 +199,7 @@ static void drop_unnamed_stmt(void);
 static void log_disconnections(int code, Datum arg);
 static void enable_statement_timeout(void);
 static void disable_statement_timeout(void);
+static void ProcessRecoveryExitInterrupt(void);
 
 
 /* ----------------------------------------------------------------
@@ -539,6 +548,10 @@ ProcessClientReadInterrupt(bool blocked)
 		/* Process notify interrupts, if any */
 		if (notifyInterruptPending)
 			ProcessNotifyInterrupt();
+
+		/* Process recovery exit interrupts that happened while reading */
+		if (recoveryExitInterruptPending)
+			ProcessRecoveryExitInterrupt();
 	}
 	else if (ProcDiePending)
 	{
@@ -3009,6 +3022,52 @@ RecoveryConflictInterrupt(ProcSignalReason reason)
 }
 
 /*
+ * HandleRecoveryExitInterrupt
+ *
+ *		Signal handler portion of interrupt handling. Let the backend know
+ *		that the server has exited the recovery mode.
+ */
+void
+HandleRecoveryExitInterrupt(void)
+{
+	/*
+	 * Note: this is called by a SIGNAL HANDLER. You must be very wary what
+	 * you do here.
+	 */
+
+	/* signal that work needs to be done */
+	recoveryExitInterruptPending = true;
+
+	/* make sure the event is processed in due course */
+	SetLatch(MyLatch);
+}
+
+/*
+ * ProcessRecoveryExitInterrupt
+ *
+ *		This is called just after waiting for a frontend command.  If a
+ *		interrupt arrives (via HandleRecoveryExitInterrupt()) while reading,
+ *		the read will be interrupted via the process's latch, and this routine
+ *		will get called.
+*/
+static void
+ProcessRecoveryExitInterrupt(void)
+{
+	recoveryExitInterruptPending = false;
+
+	SetConfigOption("in_recovery",
+					"off",
+					PGC_INTERNAL, PGC_S_OVERRIDE);
+
+	/*
+	 * Flush output buffer so that clients receive the ParameterStatus message
+	 * as soon as possible.
+	 */
+	pq_flush();
+}
+
+
+/*
  * ProcessInterrupts: out-of-line portion of CHECK_FOR_INTERRUPTS() macro
  *
  * If an interrupt condition is pending, and it's safe to service it,
diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c
index f4247ea..e8f9fd0 100644
--- a/src/backend/utils/init/postinit.c
+++ b/src/backend/utils/init/postinit.c
@@ -646,10 +646,13 @@ InitPostgres(const char *in_dbname, Oid dboid, const char *username,
 		/*
 		 * The postmaster already started the XLOG machinery, but we need to
 		 * call InitXLOGAccess(), if the system isn't in hot-standby mode.
-		 * This is handled by calling RecoveryInProgress and ignoring the
-		 * result.
+		 * This is handled by calling RecoveryInProgress.
 		 */
-		(void) RecoveryInProgress();
+		if (RecoveryInProgress())
+			SetConfigOption("in_recovery",
+							"on",
+							PGC_INTERNAL, PGC_S_OVERRIDE);
+
 	}
 	else
 	{
diff --git a/src/backend/utils/misc/check_guc b/src/backend/utils/misc/check_guc
index 416a087..a4ebcef 100755
--- a/src/backend/utils/misc/check_guc
+++ b/src/backend/utils/misc/check_guc
@@ -21,7 +21,7 @@ is_superuser lc_collate lc_ctype lc_messages lc_monetary lc_numeric lc_time \
 pre_auth_delay role seed server_encoding server_version server_version_num \
 session_authorization trace_lock_oidmin trace_lock_table trace_locks trace_lwlocks \
 trace_notify trace_userlocks transaction_isolation transaction_read_only \
-zero_damaged_pages"
+zero_damaged_pages in_recovery"
 
 ### What options are listed in postgresql.conf.sample, but don't appear
 ### in guc.c?
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 99a3e4f..6f65448 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -615,6 +615,7 @@ static char *recovery_target_string;
 static char *recovery_target_xid_string;
 static char *recovery_target_name_string;
 static char *recovery_target_lsn_string;
+static bool in_recovery;
 
 
 /* should be static, but commands/variable.c needs to get at this */
@@ -1855,6 +1856,21 @@ static struct config_bool ConfigureNamesBool[] =
 	},
 
 	{
+		/*
+		 * Not for general use --- used to indicate whether the instance is
+		 * recovery mode
+		 */
+		{"in_recovery", PGC_INTERNAL, UNGROUPED,
+			gettext_noop("Shows whether the instance is in recovery mode."),
+			NULL,
+			GUC_REPORT | GUC_NO_SHOW_ALL | GUC_NO_RESET_ALL | GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE
+		},
+		&in_recovery,
+		false,
+		NULL, NULL, NULL
+	},
+
+	{
 		{"allow_system_table_mods", PGC_SUSET, DEVELOPER_OPTIONS,
 			gettext_noop("Allows modifications of the structure of system tables."),
 			NULL,
diff --git a/src/include/storage/procarray.h b/src/include/storage/procarray.h
index a5c7d0c..08b3b04 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -113,6 +113,7 @@ extern void CancelDBBackends(Oid databaseid, ProcSignalReason sigmode, bool conf
 extern int	CountUserBackends(Oid roleid);
 extern bool CountOtherDBBackends(Oid databaseId,
 								 int *nbackends, int *nprepared);
+extern void SendSignalToAllBackends(ProcSignalReason reason);
 extern void TerminateOtherDBBackends(Oid databaseId);
 
 extern void XidCacheRemoveRunningXids(TransactionId xid,
diff --git a/src/include/storage/procsignal.h b/src/include/storage/procsignal.h
index 5cb3969..6c243d4 100644
--- a/src/include/storage/procsignal.h
+++ b/src/include/storage/procsignal.h
@@ -43,6 +43,8 @@ typedef enum
 	PROCSIG_RECOVERY_CONFLICT_BUFFERPIN,
 	PROCSIG_RECOVERY_CONFLICT_STARTUP_DEADLOCK,
 
+	PROCSIG_RECOVERY_EXIT,		/* recovery exit interrupt */
+
 	NUM_PROCSIGNALS				/* Must be last! */
 } ProcSignalReason;
 
diff --git a/src/include/storage/standby.h b/src/include/storage/standby.h
index cfbe426..e5d42ac 100644
--- a/src/include/storage/standby.h
+++ b/src/include/storage/standby.h
@@ -26,6 +26,7 @@ extern int	max_standby_streaming_delay;
 
 extern void InitRecoveryTransactionEnvironment(void);
 extern void ShutdownRecoveryTransactionEnvironment(void);
+extern void SendRecoveryExitSignal(void);
 
 extern void ResolveRecoveryConflictWithSnapshot(TransactionId latestRemovedXid,
 												RelFileNode node);
diff --git a/src/include/tcop/tcopprot.h b/src/include/tcop/tcopprot.h
index bd30607..d29dd1f 100644
--- a/src/include/tcop/tcopprot.h
+++ b/src/include/tcop/tcopprot.h
@@ -68,6 +68,8 @@ extern void StatementCancelHandler(SIGNAL_ARGS);
 extern void FloatExceptionHandler(SIGNAL_ARGS) pg_attribute_noreturn();
 extern void RecoveryConflictInterrupt(ProcSignalReason reason); /* called from SIGUSR1
 																 * handler */
+/* recovery exit interrupt handling function */
+extern void HandleRecoveryExitInterrupt(void);
 extern void ProcessClientReadInterrupt(bool blocked);
 extern void ProcessClientWriteInterrupt(bool blocked);
 
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index 7bee9dd..0005cf3 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -351,9 +351,14 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
 
 	{"target_session_attrs", "PGTARGETSESSIONATTRS",
 		DefaultTargetSessionAttrs, NULL,
-		"Target-Session-Attrs", "", 11, /* sizeof("read-write") = 11 */
+		"Target-Session-Attrs", "", 15, /* sizeof("prefer-standby") = 15 */
 	offsetof(struct pg_conn, target_session_attrs)},
 
+	{"target_server_type", "PGTARGETSERVERTYPE",
+		NULL, NULL,
+		"Target-Server-Type", "", 17, /* sizeof("prefer-secondary") = 17 */
+	offsetof(struct pg_conn, target_server_type)},
+
 	/* Terminating entry --- MUST BE LAST */
 	{NULL, NULL, NULL, NULL,
 	NULL, NULL, 0}
@@ -1001,6 +1006,30 @@ parse_comma_separated_list(char **startptr, bool *more)
 }
 
 /*
+ *		validateAndRecordTargetServerType
+ *
+ * Validate a given target server option value and record the requested server
+ * type. All valid target_server_type option values are also allowed in
+ * target_session_attrs (as a single option value).
+ *
+ * Returns true if OK, false if the specified option value is invalid.
+ */
+static bool
+validateAndRecordTargetServerType(const char *optionValue, TargetServerType *requestedServerType)
+{
+	if (strcmp(optionValue, "primary") == 0 || strcmp(optionValue, "read-write") == 0)
+		*requestedServerType = SERVER_TYPE_PRIMARY;
+	else if (strcmp(optionValue, "prefer-standby") == 0 || strcmp(optionValue, "prefer-secondary") == 0)
+		*requestedServerType = SERVER_TYPE_PREFER_STANDBY;
+	else if (strcmp(optionValue, "standby") == 0 || strcmp(optionValue, "secondary") == 0)
+		*requestedServerType = SERVER_TYPE_STANDBY;
+	else
+		return false;
+
+	return true;
+}
+
+/*
  *		connectOptions2
  *
  * Compute derived connection options after absorbing all user-supplied info.
@@ -1396,8 +1425,9 @@ connectOptions2(PGconn *conn)
 	 */
 	if (conn->target_session_attrs)
 	{
-		if (strcmp(conn->target_session_attrs, "any") != 0
-			&& strcmp(conn->target_session_attrs, "read-write") != 0)
+		if (strcmp(conn->target_session_attrs, "any") == 0)
+			conn->requested_server_type = SERVER_TYPE_ANY;
+		else if (!validateAndRecordTargetServerType(conn->target_session_attrs, &conn->requested_server_type))
 		{
 			conn->status = CONNECTION_BAD;
 			printfPQExpBuffer(&conn->errorMessage,
@@ -1409,6 +1439,23 @@ connectOptions2(PGconn *conn)
 	}
 
 	/*
+	 * Validate target_server_type option.
+	 * If a target_server_type is specified, it overrides any target server
+	 * type specified in target_session_attrs.
+	 */
+	if (conn->target_server_type)
+	{
+		if (!validateAndRecordTargetServerType(conn->target_server_type, &conn->requested_server_type))
+		{
+			conn->status = CONNECTION_BAD;
+			printfPQExpBuffer(&conn->errorMessage,
+							  libpq_gettext("invalid target_server_type value: \"%s\"\n"),
+							  conn->target_server_type);
+			return false;
+		}
+	}
+
+	/*
 	 * Only if we get this far is it appropriate to try to connect. (We need a
 	 * state flag, rather than just the boolean result of this function, in
 	 * case someone tries to PQreset() the PGconn.)
@@ -2228,6 +2275,110 @@ restoreErrorMessage(PGconn *conn, PQExpBuffer savedMessage)
 	termPQExpBuffer(savedMessage);
 }
 
+/*
+ * Internal helper function used for rejecting (and closing) a connection that
+ * doesn't satisfy the requested server type. The connection state is set to
+ * try the next host (if any).
+ * In the case of SERVER_TYPE_PREFER_STANDBY, if the read-write host-index hasn't
+ * been set, then it is set to the index of this connection's host, so that a
+ * connection to this host can be made again in the event that no connection to
+ * a standby host could be made after the first host scan.
+ */
+static void
+rejectCheckedReadOrWriteConnection(PGconn *conn)
+{
+	/* Not a requested type; fail this connection. */
+	const char *displayed_host;
+	const char *displayed_port;
+
+	/* Append error report to conn->errorMessage. */
+	if (conn->connhost[conn->whichhost].type == CHT_HOST_ADDRESS)
+		displayed_host = conn->connhost[conn->whichhost].hostaddr;
+	else
+		displayed_host = conn->connhost[conn->whichhost].host;
+	displayed_port = conn->connhost[conn->whichhost].port;
+	if (displayed_port == NULL || displayed_port[0] == '\0')
+		displayed_port = DEF_PGPORT_STR;
+
+	if (conn->requested_server_type == SERVER_TYPE_PRIMARY)
+		appendPQExpBuffer(&conn->errorMessage,
+						  libpq_gettext("could not make a writable "
+										"connection to server "
+										"\"%s:%s\"\n"),
+						  displayed_host, displayed_port);
+	else
+		appendPQExpBuffer(&conn->errorMessage,
+						  libpq_gettext("could not make a readonly "
+										"connection to server "
+										"\"%s:%s\"\n"),
+						  displayed_host, displayed_port);
+
+	/* Close connection politely. */
+	conn->status = CONNECTION_OK;
+	sendTerminateConn(conn);
+
+	/* Record read-write host index */
+	if (conn->requested_server_type == SERVER_TYPE_PREFER_STANDBY && conn->which_rw_host == -1)
+		conn->which_rw_host = conn->whichhost;
+
+	/*
+	 * Try next host if any, but we don't want to consider additional
+	 * addresses for this host.
+	 */
+	conn->try_next_host = true;
+}
+
+/*
+ * Internal helper function used for rejecting (and closing) a connection that
+ * doesn't satisfy the requested server type (for recovery). The connection state
+ * is set to try the next host (if any).
+ * In the case of SERVER_TYPE_PREFER_STANDBY, if the read-write host-index hasn't
+ * been set, then it is set to the index of this connection's host, so that a
+ * connection to this host can be made again in the event that no connection to
+ * a standby host could be made after the first host scan.
+ */
+static void
+rejectCheckedRecoveryConnection(PGconn *conn)
+{
+	/* Not a requested type; fail this connection. */
+	const char *displayed_host;
+	const char *displayed_port;
+
+	/* Append error report to conn->errorMessage. */
+	if (conn->connhost[conn->whichhost].type == CHT_HOST_ADDRESS)
+		displayed_host = conn->connhost[conn->whichhost].hostaddr;
+	else
+		displayed_host = conn->connhost[conn->whichhost].host;
+	displayed_port = conn->connhost[conn->whichhost].port;
+	if (displayed_port == NULL || displayed_port[0] == '\0')
+		displayed_port = DEF_PGPORT_STR;
+
+	if (conn->requested_server_type == SERVER_TYPE_PRIMARY)
+		appendPQExpBuffer(&conn->errorMessage,
+						  libpq_gettext("server is in recovery mode "
+										"\"%s:%s\"\n"),
+						  displayed_host, displayed_port);
+	else
+		appendPQExpBuffer(&conn->errorMessage,
+						  libpq_gettext("server is not in recovery mode "
+										"\"%s:%s\"\n"),
+						  displayed_host, displayed_port);
+
+	/* Close connection politely. */
+	conn->status = CONNECTION_OK;
+	sendTerminateConn(conn);
+
+	/* Record read-write host index */
+	if (conn->requested_server_type == SERVER_TYPE_PREFER_STANDBY && conn->which_rw_host == -1)
+		conn->which_rw_host = conn->whichhost;
+
+	/*
+	 * Try next host if any, but we don't want to consider additional
+	 * addresses for this host.
+	 */
+	conn->try_next_host = true;
+}
+
 /* ----------------
  *		PQconnectPoll
  *
@@ -2310,6 +2461,7 @@ PQconnectPoll(PGconn *conn)
 		case CONNECTION_CHECK_WRITABLE:
 		case CONNECTION_CONSUME:
 		case CONNECTION_GSS_STARTUP:
+		case CONNECTION_CHECK_RECOVERY:
 			break;
 
 		default:
@@ -2346,12 +2498,31 @@ keep_going:						/* We will come back to here until there is
 
 		if (conn->whichhost + 1 >= conn->nconnhost)
 		{
-			/*
-			 * Oops, no more hosts.  An appropriate error message is already
-			 * set up, so just set the right status.
-			 */
-			goto error_return;
+			if (conn->which_rw_host >= 0)
+			{
+				/*
+				 * Getting here means we failed to connect to read-only servers
+				 * and should now try to re-connect to a previously-connected-to
+				 * read-write server, whose host index is recorded in which_rw_host.
+				 */
+				conn->whichhost = conn->which_rw_host;
+
+				/*
+				 * Reset the host index value to avoid recursion during the
+				 * second connection attempt.
+				 */
+				conn->which_rw_host = -2;
+			}
+			else
+			{
+				/*
+				 * Oops, no more hosts.  An appropriate error message is
+				 * already set up, so just set the right status.
+				 */
+				goto error_return;
+			}
 		}
+		else
 		conn->whichhost++;
 
 		/* Drop any address info for previous host */
@@ -3560,38 +3731,102 @@ keep_going:						/* We will come back to here until there is
 		case CONNECTION_CHECK_TARGET:
 			{
 				/*
-				 * If a read-write connection is required, see if we have one.
+				 * If a primary (not-in-recovery / read-write) connection is
+				 * required, see if we have one.
 				 *
 				 * Servers before 7.4 lack the transaction_read_only GUC, but
 				 * by the same token they don't have any read-only mode, so we
 				 * may just skip the test in that case.
 				 */
-				if (conn->sversion >= 70400 &&
-					conn->target_session_attrs != NULL &&
-					strcmp(conn->target_session_attrs, "read-write") == 0)
+				if ((conn->sversion >= 70400 &&
+					  (conn->requested_server_type == SERVER_TYPE_PRIMARY ||
+					   conn->requested_server_type == SERVER_TYPE_PREFER_STANDBY ||
+					   conn->requested_server_type == SERVER_TYPE_STANDBY)))
+				{
+					if (conn->sversion < 140000)
+					{
+						/*
+						 * Save existing error messages across the PQsendQuery
+						 * attempt.  This is necessary because PQsendQuery is
+						 * going to reset conn->errorMessage, so we would lose
+						 * error messages related to previous hosts we have
+						 * tried and failed to connect to.
+						 */
+						if (!saveErrorMessage(conn, &savedMessage))
+							goto error_return;
+
+						conn->status = CONNECTION_OK;
+						if (!PQsendQuery(conn, "SHOW transaction_read_only"))
+						{
+							restoreErrorMessage(conn, &savedMessage);
+							goto error_return;
+						}
+						conn->status = CONNECTION_CHECK_WRITABLE;
+						restoreErrorMessage(conn, &savedMessage);
+						return PGRES_POLLING_READING;
+					}
+					else if ((conn->in_recovery &&
+						  conn->requested_server_type == SERVER_TYPE_PRIMARY) ||
+						 (!conn->in_recovery &&
+						  (conn->requested_server_type == SERVER_TYPE_PREFER_STANDBY ||
+						   conn->requested_server_type == SERVER_TYPE_STANDBY)))
+					{
+						/*
+						 * The following scenario is possible only for the
+						 * prefer-standby type for the next pass of the list
+						 * of connections, as it couldn't find any servers that
+						 * are in recovery.
+						 */
+						if (conn->which_rw_host == -2)
+							goto consume_checked_target_connection;
+
+						rejectCheckedRecoveryConnection(conn);
+						goto keep_going;
+					}
+
+					/* obtained the requested type, consume it */
+					goto consume_checked_target_connection;
+				}
+
+				/*
+				 * If the Requested type of connection is prefer-standby, then record
+				 * this host index and try other specified hosts before considering it later.
+				 * If the requested type of connection is standby, ignore this connection.
+				 */
+
+				if (conn->requested_server_type == SERVER_TYPE_PREFER_STANDBY ||
+					conn->requested_server_type == SERVER_TYPE_STANDBY)
 				{
 					/*
-					 * Save existing error messages across the PQsendQuery
-					 * attempt.  This is necessary because PQsendQuery is
-					 * going to reset conn->errorMessage, so we would lose
-					 * error messages related to previous hosts we have tried
-					 * and failed to connect to.
+					 * The following scenario is possible only for the
+					 * prefer-standby mode for the next pass of the list
+					 * of connections, as it couldn't find any servers that
+					 * are in recovery.
 					 */
-					if (!saveErrorMessage(conn, &savedMessage))
-						goto error_return;
+					if (conn->which_rw_host == -2)
+						goto consume_checked_target_connection;
 
+					/* Close connection politely. */
 					conn->status = CONNECTION_OK;
-					if (!PQsendQuery(conn,
-									 "SHOW transaction_read_only"))
+					sendTerminateConn(conn);
+
+					/* Record read-write host index */
+					if (conn->requested_server_type == SERVER_TYPE_PREFER_STANDBY)
 					{
-						restoreErrorMessage(conn, &savedMessage);
-						goto error_return;
+						if (conn->which_rw_host == -1)
+							conn->which_rw_host = conn->whichhost;
 					}
-					conn->status = CONNECTION_CHECK_WRITABLE;
-					restoreErrorMessage(conn, &savedMessage);
-					return PGRES_POLLING_READING;
+
+					/*
+					 * Try next host if any, but we don't want to consider
+					 * additional addresses for this host.
+					 */
+					conn->try_next_host = true;
+					goto keep_going;
 				}
 
+		consume_checked_target_connection:
+
 				/* We can release the address list now. */
 				release_conn_addrinfo(conn);
 
@@ -3690,42 +3925,149 @@ keep_going:						/* We will come back to here until there is
 					PQntuples(res) == 1)
 				{
 					char	   *val;
+					bool		readonly_server;
 
 					val = PQgetvalue(res, 0, 0);
-					if (strncmp(val, "on", 2) == 0)
+					readonly_server = (strncmp(val, "on", 2) == 0);
+
+					/*
+					 * Server is read-only and requested server type is primary,
+					 * ignore it. Server is read-write and requested type is
+					 * prefer-standby, record it for the first time and try to
+					 * consume in the next scan (it means no standby server
+					 * was found in the first scan). Server is read-write and
+					 * requested type is standby, ignore this connection.
+					 */
+					if ((readonly_server &&
+						 conn->requested_server_type == SERVER_TYPE_PRIMARY) ||
+						(!readonly_server &&
+						 (conn->requested_server_type == SERVER_TYPE_PREFER_STANDBY ||
+						  conn->requested_server_type == SERVER_TYPE_STANDBY)))
 					{
-						/* Not writable; fail this connection. */
+						/*
+						 * The following scenario is possible only for the
+						 * prefer-standby type for the next pass of the list of
+						 * connections, as it couldn't find any servers that
+						 * are default read-only.
+						 */
+						if (conn->which_rw_host == -2)
+							goto consume_checked_write_connection;
+
+						/* Not a requested type; fail this connection. */
 						PQclear(res);
 						restoreErrorMessage(conn, &savedMessage);
 
-						/* Append error report to conn->errorMessage. */
-						if (conn->connhost[conn->whichhost].type == CHT_HOST_ADDRESS)
-							displayed_host = conn->connhost[conn->whichhost].hostaddr;
-						else
-							displayed_host = conn->connhost[conn->whichhost].host;
-						displayed_port = conn->connhost[conn->whichhost].port;
-						if (displayed_port == NULL || displayed_port[0] == '\0')
-							displayed_port = DEF_PGPORT_STR;
+						rejectCheckedReadOrWriteConnection(conn);
+						goto keep_going;
+					}
 
-						appendPQExpBuffer(&conn->errorMessage,
-										  libpq_gettext("could not make a writable "
-														"connection to server "
-														"\"%s:%s\"\n"),
-										  displayed_host, displayed_port);
+			consume_checked_write_connection:
+					/* Session is requested type, so we're good. */
+					PQclear(res);
+					termPQExpBuffer(&savedMessage);
 
-						/* Close connection politely. */
-						conn->status = CONNECTION_OK;
-						sendTerminateConn(conn);
+					/*
+					 * Finish reading any remaining messages before being
+					 * considered as ready.
+					 */
+					conn->status = CONNECTION_CONSUME;
+					goto keep_going;
+				}
+
+				/*
+				 * Something went wrong with "SHOW transaction_read_only". We
+				 * should try next addresses.
+				 */
+				if (res)
+					PQclear(res);
+				restoreErrorMessage(conn, &savedMessage);
+
+				/* Append error report to conn->errorMessage. */
+				if (conn->connhost[conn->whichhost].type == CHT_HOST_ADDRESS)
+					displayed_host = conn->connhost[conn->whichhost].hostaddr;
+				else
+					displayed_host = conn->connhost[conn->whichhost].host;
+				displayed_port = conn->connhost[conn->whichhost].port;
+				if (displayed_port == NULL || displayed_port[0] == '\0')
+					displayed_port = DEF_PGPORT_STR;
+				appendPQExpBuffer(&conn->errorMessage,
+								  libpq_gettext("test \"SHOW transaction_read_only\" failed "
+												"on server \"%s:%s\"\n"),
+								  displayed_host, displayed_port);
+
+				/* Close connection politely. */
+				conn->status = CONNECTION_OK;
+				sendTerminateConn(conn);
+
+				/* Try next address */
+				conn->try_next_addr = true;
+				goto keep_going;
+			}
+
+		case CONNECTION_CHECK_RECOVERY:
+			{
+				const char *displayed_host;
+				const char *displayed_port;
+
+				if (!saveErrorMessage(conn, &savedMessage))
+					goto error_return;
+
+				conn->status = CONNECTION_OK;
+				if (!PQconsumeInput(conn))
+				{
+					restoreErrorMessage(conn, &savedMessage);
+					goto error_return;
+				}
 
+				if (PQisBusy(conn))
+				{
+					conn->status = CONNECTION_CHECK_RECOVERY;
+					restoreErrorMessage(conn, &savedMessage);
+					return PGRES_POLLING_READING;
+				}
+
+				res = PQgetResult(conn);
+				if (res && (PQresultStatus(res) == PGRES_TUPLES_OK) &&
+					PQntuples(res) == 1)
+				{
+					char	   *val;
+					bool		standby_server;
+
+					val = PQgetvalue(res, 0, 0);
+					standby_server = (strncmp(val, "t", 1) == 0);
+
+					/*
+					 * Server is in recovery mode and requested type is
+					 * primary, ignore it. Server is not in recovery mode and
+					 * requested type is prefer-standby, record it for the
+					 * first time and try to consume in the next scan (it
+					 * means no standby server was found in the first scan).
+					 */
+					if ((standby_server &&
+						 conn->requested_server_type == SERVER_TYPE_PRIMARY) ||
+						(!standby_server &&
+						 (conn->requested_server_type == SERVER_TYPE_PREFER_STANDBY ||
+						  conn->requested_server_type == SERVER_TYPE_STANDBY)))
+					{
 						/*
-						 * Try next host if any, but we don't want to consider
-						 * additional addresses for this host.
+						 * The following scenario is possible only for the
+						 * prefer-standby type for the next pass of the list
+						 * of connections, as it couldn't find any servers that
+						 * are in recovery.
 						 */
-						conn->try_next_host = true;
+						if (conn->which_rw_host == -2)
+							goto consume_checked_recovery_connection;
+
+						/* Not a requested type; fail this connection. */
+						PQclear(res);
+						restoreErrorMessage(conn, &savedMessage);
+
+						rejectCheckedRecoveryConnection(conn);
 						goto keep_going;
 					}
 
-					/* Session is read-write, so we're good. */
+			consume_checked_recovery_connection:
+					/* Session is requested type, so we're good. */
 					PQclear(res);
 					termPQExpBuffer(&savedMessage);
 
@@ -3738,7 +4080,7 @@ keep_going:						/* We will come back to here until there is
 				}
 
 				/*
-				 * Something went wrong with "SHOW transaction_read_only". We
+				 * Something went wrong with "SELECT pg_is_in_recovery()". We
 				 * should try next addresses.
 				 */
 				if (res)
@@ -3754,7 +4096,7 @@ keep_going:						/* We will come back to here until there is
 				if (displayed_port == NULL || displayed_port[0] == '\0')
 					displayed_port = DEF_PGPORT_STR;
 				appendPQExpBuffer(&conn->errorMessage,
-								  libpq_gettext("test \"SHOW transaction_read_only\" failed "
+								  libpq_gettext("test \"SELECT pg_is_in_recovery()\" failed "
 												"on server \"%s:%s\"\n"),
 								  displayed_host, displayed_port);
 
@@ -3907,6 +4249,9 @@ makeEmptyPGconn(void)
 	conn->show_context = PQSHOW_CONTEXT_ERRORS;
 	conn->sock = PGINVALID_SOCKET;
 
+	conn->requested_server_type = SERVER_TYPE_ANY;
+	conn->which_rw_host = -1;
+
 	/*
 	 * We try to send at least 8K at a time, which is the usual size of pipe
 	 * buffers on Unix systems.  That way, when we are sending a large amount
diff --git a/src/interfaces/libpq/fe-exec.c b/src/interfaces/libpq/fe-exec.c
index eea0237..be73aa7 100644
--- a/src/interfaces/libpq/fe-exec.c
+++ b/src/interfaces/libpq/fe-exec.c
@@ -1058,7 +1058,7 @@ pqSaveParameterStatus(PGconn *conn, const char *name, const char *value)
 	}
 
 	/*
-	 * Special hacks: remember client_encoding and
+	 * Special hacks: remember client_encoding, and
 	 * standard_conforming_strings, and convert server version to a numeric
 	 * form.  We keep the first two of these in static variables as well, so
 	 * that PQescapeString and PQescapeBytea can behave somewhat sanely (at
@@ -1112,6 +1112,10 @@ pqSaveParameterStatus(PGconn *conn, const char *name, const char *value)
 		else
 			conn->sversion = 0; /* unknown */
 	}
+	else if (strcmp(name, "in_recovery") == 0)
+	{
+		conn->in_recovery = (strcmp(value, "on") == 0);
+	}
 }
 
 
diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h
index 3b6a9fb..9c59feb 100644
--- a/src/interfaces/libpq/libpq-fe.h
+++ b/src/interfaces/libpq/libpq-fe.h
@@ -68,7 +68,8 @@ typedef enum
 	CONNECTION_CONSUME,			/* Wait for any pending message and consume
 								 * them. */
 	CONNECTION_GSS_STARTUP,		/* Negotiating GSSAPI. */
-	CONNECTION_CHECK_TARGET		/* Check if we have a proper target connection */
+	CONNECTION_CHECK_TARGET,	/* Check if we have a proper target connection */
+	CONNECTION_CHECK_RECOVERY	/* Check whether server is in recovery */
 } ConnStatusType;
 
 typedef enum
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 1de91ae..8033fc0 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -317,6 +317,15 @@ typedef struct pg_conn_host
 								 * found in password file. */
 } pg_conn_host;
 
+/* Target server type to connect to */
+typedef enum
+{
+	SERVER_TYPE_ANY = 0,           /* Any server (default) */
+	SERVER_TYPE_PRIMARY,           /* Primary server */
+	SERVER_TYPE_PREFER_STANDBY,    /* Prefer Standby server */
+	SERVER_TYPE_STANDBY            /* Standby server */
+} TargetServerType;
+
 /*
  * PGconn stores all the state data associated with a single connection
  * to a backend.
@@ -370,9 +379,33 @@ struct pg_conn
 	char	   *ssl_min_protocol_version;	/* minimum TLS protocol version */
 	char	   *ssl_max_protocol_version;	/* maximum TLS protocol version */
 
-	/* Type of connection to make.  Possible values: any, read-write. */
+	/*
+	 * Type of connection to make.  Possible values:
+	 * 	"any"
+	 * 	"primary" (or "read-write")
+	 * 	"prefer-standby" (or "prefer-secondary")
+	 * 	"standby" (or "secondary").
+	 */
 	char	   *target_session_attrs;
 
+	/*
+	 * Type of server to connect to. Possible values:
+	 * 	"primary" (or "read-write")
+	 * 	"prefer-standby" (or "prefer-secondary")
+	 * 	"standby" (or "secondary").
+	 * This overrides any connection type specified by target_session_attrs.
+	 * This option is almost a synonym for the target_session_attrs option, except
+	 * its purpose is to closely reflect the similar PGJDBC targetServerType option.
+	 * Note also that this option only accepts single option values, whereas in
+	 * future, target_session_attrs may accept multiple session attribute values.
+	 */
+	char	   *target_server_type;
+
+	/*
+	 * The requested server type, derived from target_session_attrs / target_server_type.
+	 */
+	TargetServerType requested_server_type;
+
 	/* Optional file to write trace info to */
 	FILE	   *Pfdebug;
 
@@ -406,6 +439,17 @@ struct pg_conn
 	pg_conn_host *connhost;		/* details about each named host */
 	char	   *connip;			/* IP address for current network connection */
 
+	/*
+	 * Index of the first read-write host encountered (if any) in the connection string.
+	 *
+	 * The initial value is -1, indicating that no read-write host has yet been found.
+	 * It is then set to the index of the first read-write host, if one is found in the
+	 * connection string during processing. If a second connection attempt is later made
+	 * to that read-write host, which_rw_host is then set to -2 to avoid recursion during
+	 * processing (and whichhost is set to the read-write host index).
+	 */
+	int			which_rw_host;
+
 	/* Connection data */
 	pgsocket	sock;			/* FD for socket, PGINVALID_SOCKET if
 								 * unconnected */
@@ -436,6 +480,7 @@ struct pg_conn
 	pgParameterStatus *pstatus; /* ParameterStatus data */
 	int			client_encoding;	/* encoding id */
 	bool		std_strings;	/* standard_conforming_strings */
+	bool		in_recovery;	/* in_recovery */
 	PGVerbosity verbosity;		/* error/notice message verbosity */
 	PGContextVisibility show_context;	/* whether to show CONTEXT field */
 	PGlobjfuncs *lobjfuncs;		/* private state for large-object access fns */
@@ -540,7 +585,6 @@ struct pg_cancel
 	int			be_key;			/* key of backend --- needed for cancels */
 };
 
-
 /* String descriptions of the ExecStatusTypes.
  * direct use of this array is deprecated; call PQresStatus() instead.
  */
diff --git a/src/test/recovery/t/001_stream_rep.pl b/src/test/recovery/t/001_stream_rep.pl
index 9e31a53..707d4ab 100644
--- a/src/test/recovery/t/001_stream_rep.pl
+++ b/src/test/recovery/t/001_stream_rep.pl
@@ -3,7 +3,7 @@ use strict;
 use warnings;
 use PostgresNode;
 use TestLib;
-use Test::More tests => 36;
+use Test::More tests => 62;
 
 # Initialize primary node
 my $node_primary = get_new_node('primary');
@@ -121,6 +121,150 @@ test_target_session_attrs($node_primary, $node_standby_1, $node_primary, "any",
 test_target_session_attrs($node_standby_1, $node_primary, $node_standby_1,
 	"any", 0);
 
+# Connect to primary in "primary" mode with primary,standby1 list.
+test_target_session_attrs($node_primary, $node_standby_1, $node_primary,
+	"primary", 0);
+
+# Connect to primary in "primary" mode with standby1,primary list.
+test_target_session_attrs($node_standby_1, $node_primary, $node_primary,
+	"primary", 0);
+
+# Connect to primary in "prefer-standby" mode with primary,primary list.
+test_target_session_attrs($node_primary, $node_primary, $node_primary,
+	"prefer-standby", 0);
+
+# Connect to standby1 in "prefer-standby" mode with primary,standby1 list.
+test_target_session_attrs($node_primary, $node_standby_1, $node_standby_1,
+	"prefer-standby", 0);
+
+# Connect to standby1 in "prefer-standby" mode with standby1,primary list.
+test_target_session_attrs($node_standby_1, $node_primary, $node_standby_1,
+	"prefer-standby", 0);
+
+# Connect to primary in "prefer-secondary" mode with primary,primary list.
+test_target_session_attrs($node_primary, $node_primary, $node_primary,
+	"prefer-secondary", 0);
+
+# Connect to standby1 in "prefer-secondary" mode with primary,standby1 list.
+test_target_session_attrs($node_primary, $node_standby_1, $node_standby_1,
+	"prefer-secondary", 0);
+
+# Connect to standby1 in "prefer-ssecondary" mode with standby1,primary list.
+test_target_session_attrs($node_standby_1, $node_primary, $node_standby_1,
+	"prefer-secondary", 0);
+
+# Connect to standby1 in "standby" mode with primary,standby1 list.
+test_target_session_attrs($node_primary, $node_standby_1, $node_standby_1,
+	"standby", 0);
+
+# Connect to standby1 in "standby" mode with standby1,primary list.
+test_target_session_attrs($node_standby_1, $node_primary, $node_standby_1,
+	"standby", 0);
+
+# Connect to standby1 in "secondary" mode with primary,standby1 list.
+test_target_session_attrs($node_primary, $node_standby_1, $node_standby_1,
+	"secondary", 0);
+
+# Connect to standby1 in "secondary" mode with standby1,primary list.
+test_target_session_attrs($node_standby_1, $node_primary, $node_standby_1,
+	"secondary", 0);
+
+# Tests for connection parameter target_server_type
+note "testing connection parameter \"target_server_type\"";
+
+# Routine designed to run tests on the connection parameter
+# target_server_type with multiple nodes.
+sub test_target_server_type
+{
+	my $node1       = shift;
+	my $node2       = shift;
+	my $target_node = shift;
+	my $mode        = shift;
+	my $status      = shift;
+
+	my $node1_host = $node1->host;
+	my $node1_port = $node1->port;
+	my $node1_name = $node1->name;
+	my $node2_host = $node2->host;
+	my $node2_port = $node2->port;
+	my $node2_name = $node2->name;
+
+	my $target_name = $target_node->name;
+
+	# Build connection string for connection attempt.
+	my $connstr = "host=$node1_host,$node2_host ";
+	$connstr .= "port=$node1_port,$node2_port ";
+	$connstr .= "target_server_type=$mode";
+
+	# The client used for the connection does not matter, only the backend
+	# point does.
+	my ($ret, $stdout, $stderr) =
+	  $node1->psql('postgres', 'SHOW port;',
+		extra_params => [ '-d', $connstr ]);
+	is( $status == $ret && $stdout eq $target_node->port,
+		1,
+		"connect to node $target_name if mode \"$mode\" and $node1_name,$node2_name listed"
+	);
+
+	return;
+}
+
+# Connect to primary in "read-write" mode with primary,standby1 list.
+test_target_server_type($node_primary, $node_standby_1, $node_primary,
+	"read-write", 0);
+
+# Connect to primary in "read-write" mode with standby1,primary list.
+test_target_server_type($node_standby_1, $node_primary, $node_primary,
+	"read-write", 0);
+
+# Connect to primary in "primary" mode with primary,standby1 list.
+test_target_server_type($node_primary, $node_standby_1, $node_primary,
+	"primary", 0);
+
+# Connect to primary in "primary" mode with standby1,primary list.
+test_target_server_type($node_standby_1, $node_primary, $node_primary,
+	"primary", 0);
+
+# Connect to primary in "prefer-standby" mode with primary,primary list.
+test_target_server_type($node_primary, $node_primary, $node_primary,
+	"prefer-standby", 0);
+
+# Connect to standby1 in "prefer-standby" mode with primary,standby1 list.
+test_target_server_type($node_primary, $node_standby_1, $node_standby_1,
+	"prefer-standby", 0);
+
+# Connect to standby1 in "prefer-standby" mode with standby1,primary list.
+test_target_server_type($node_standby_1, $node_primary, $node_standby_1,
+	"prefer-standby", 0);
+
+# Connect to primary in "prefer-secondary" mode with primary,primary list.
+test_target_server_type($node_primary, $node_primary, $node_primary,
+	"prefer-secondary", 0);
+
+# Connect to standby1 in "prefer-secondary" mode with primary,standby1 list.
+test_target_server_type($node_primary, $node_standby_1, $node_standby_1,
+	"prefer-secondary", 0);
+
+# Connect to standby1 in "prefer-secondary" mode with standby1,primary list.
+test_target_server_type($node_standby_1, $node_primary, $node_standby_1,
+	"prefer-secondary", 0);
+
+# Connect to standby1 in "standby" mode with primary,standby1 list.
+test_target_server_type($node_primary, $node_standby_1, $node_standby_1,
+	"standby", 0);
+
+# Connect to standby1 in "standby" mode with standby1,primary list.
+test_target_server_type($node_standby_1, $node_primary, $node_standby_1,
+	"standby", 0);
+
+# Connect to standby1 in "secondary" mode with primary,standby1 list.
+test_target_server_type($node_primary, $node_standby_1, $node_standby_1,
+	"secondary", 0);
+
+# Connect to standby1 in "secondary" mode with standby1,primary list.
+test_target_server_type($node_standby_1, $node_primary, $node_standby_1,
+	"secondary", 0);
+
 # Test for SHOW commands using a WAL sender connection with a replication
 # role.
 note "testing SHOW commands for replication connection";
-- 
1.8.3.1

