From c9da78a38611d1b3ac5d259d8c1d4ca01ac01658 Mon Sep 17 00:00:00 2001
From: Greg Nancarrow <gregn4422@gmail.com>
Date: Tue, 18 Aug 2020 20:48:43 +1000
Subject: [PATCH v18] Enhance libpq target_session_attrs and add
 target_server_type.

Enhance the connection parameter "target_session_attrs" to support new values
read-only/primary/standby/prefer-standby. 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. Similarly, enhance "transaction_read_only" to be a GUC_REPORT
variable, for client connections to read-only/read-write servers.

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                        | 105 +++++-
 doc/src/sgml/protocol.sgml                     |   9 +-
 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                    |  60 ++++
 src/backend/utils/init/postinit.c              |   9 +-
 src/backend/utils/misc/check_guc               |   2 +-
 src/backend/utils/misc/guc.c                   |  18 +-
 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              | 464 ++++++++++++++++++++++---
 src/interfaces/libpq/fe-exec.c                 |  10 +-
 src/interfaces/libpq/libpq-int.h               |  55 ++-
 src/test/recovery/t/001_stream_rep.pl          | 202 ++++++++++-
 20 files changed, 900 insertions(+), 90 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 d6f79fc..afb80e6 100644
--- a/doc/src/sgml/high-availability.sgml
+++ b/doc/src/sgml/high-availability.sgml
@@ -1885,8 +1885,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..de31b34 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -1811,18 +1811,81 @@ 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>read-write</literal>, <literal>read-only</literal>,
+        <literal>primary</literal>, <literal>standby</literal> and
+        <literal>prefer-standby</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>read-write</literal>, only a connection in which
+        read-write transactions are accepted by default is considered acceptable. To determine
+        whether the server supports read-write transactions, then if the server is version 14
+        or greater, the support of read-write transactions is determined by the value of the
+        <varname>transaction_read_only</varname> configuration parameter that is reported by
+        the server upon successful connection. Otherwise if the server is prior to version 14,
+        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>read-only</literal>, only a connection
+        in which read-only transactions are accepted by default is considered acceptable.
+       </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 (or only read-only 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>, then if the server is version 14 or
+        greater, only a connection in which the server is in recovery mode is considered acceptable.
+        Otherwise, if the server is prior to version 14, only a connection for which the server only
+        supports read-only transactions is considered acceptable.
+       </para>
+
+       <para>
+        If this parameter is set to <literal>prefer-standby</literal>, then if the server is version
+        14 or greater, a connection in which the server is in recovery mode is preferred. Otherwise,
+        if the server is prior to version 14, a connection for which the server only supports
+        read-only transactions is preferred. If no such connections can be found, then a connection
+        in which the server is not in recovery mode (server is version 14 or greater) or a
+        connection for which the server supports read-write transactions (server is prior to version
+        14) 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 purpose of this parameter is to reflect the similar PGJDBC <literal>targetServerType</literal>.
+        The supported options are a subset of those for <literal>target_session_attrs</literal>, namely
+        <literal>any</literal>, <literal>primary</literal>, <literal>secondary</literal> and
+        <literal>prefer-secondary</literal>. This parameter overrides any connection type specified by
+        <literal>target_session_attrs</literal>.
+       </para>
+      </listitem>
+     </varlistentry>
     </variablelist>
    </para>
   </sect2>
@@ -2130,14 +2193,18 @@ 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>,
+       <varname>transaction_read_only</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>transaction_read_only</varname> and <varname>in_recovery</varname>
+       were not reported by releases before 14.0.)
        Note that
        <varname>server_version</varname>,
        <varname>server_encoding</varname> and
@@ -7237,6 +7304,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..643d171 100644
--- a/doc/src/sgml/protocol.sgml
+++ b/doc/src/sgml/protocol.sgml
@@ -1283,14 +1283,17 @@ 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>,
+    <varname>transaction_read_only</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>transaction_read_only</varname> and <varname>in_recovery</varname> were 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 09c01ed..4129069 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -7940,6 +7940,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 e687cde..0af2e30 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -3682,6 +3682,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 52b2809..347b32d 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..9fa16ec 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -163,6 +163,15 @@ 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;
 static StringInfoData row_description_buf;
@@ -191,6 +200,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 +549,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 +3023,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.
+	 */
+
+	/* flag 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 d4ab4c7..ce2d0b9 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 de87ad6..f33cb68 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 */
@@ -1618,7 +1619,7 @@ static struct config_bool ConfigureNamesBool[] =
 		{"transaction_read_only", PGC_USERSET, CLIENT_CONN_STATEMENT,
 			gettext_noop("Sets the current transaction's read-only status."),
 			NULL,
-			GUC_NO_RESET_ALL | GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE
+			GUC_REPORT, GUC_NO_RESET_ALL | GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE
 		},
 		&XactReadOnly,
 		false,
@@ -1845,6 +1846,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 ea8a876..d75db13 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -80,6 +80,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 faaf1d3..d5c822d 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..4326580 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,58 @@ parse_comma_separated_list(char **startptr, bool *more)
 }
 
 /*
+ *		validateAndGetTargetServerType
+ *
+ * Validate a given target_server_type option value and get the requested server type.
+ *
+ * Returns true if OK, false if the specified option value is invalid.
+ */
+static bool
+validateAndGetTargetServerType(const char *optionValue, TargetServerType *requestedServerType)
+{
+	if (strcmp(optionValue, "any") == 0)
+		*requestedServerType = SERVER_TYPE_ANY;
+	else if (strcmp(optionValue, "primary") == 0)
+		*requestedServerType = SERVER_TYPE_PRIMARY;
+	else if (strcmp(optionValue, "prefer-secondary") == 0)
+		*requestedServerType = SERVER_TYPE_PREFER_STANDBY;
+	else if (strcmp(optionValue, "secondary") == 0)
+		*requestedServerType = SERVER_TYPE_STANDBY;
+	else
+		return false;
+
+	return true;
+}
+
+/*
+ *		validateAndGetTargetServerTypeFromSessionAttrs
+ *
+ * Validate a given target_session_attrs value and get 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
+validateAndGetTargetServerTypeFromSessionAttrs(const char *optionValue, TargetServerType *requestedServerType)
+{
+	if (!validateAndGetTargetServerType(optionValue, requestedServerType))
+	{
+		if (strcmp(optionValue, "read-write") == 0)
+			*requestedServerType = SERVER_TYPE_READ_WRITE;
+		else if (strcmp(optionValue, "read-only") == 0)
+			*requestedServerType = SERVER_TYPE_READ_ONLY;
+		else if (strcmp(optionValue, "prefer-standby") == 0)
+			*requestedServerType = SERVER_TYPE_PREFER_STANDBY;
+		else if (strcmp(optionValue, "standby") == 0)
+			*requestedServerType = SERVER_TYPE_STANDBY;
+		else
+			return false;
+	}
+	return true;
+}
+
+/*
  *		connectOptions2
  *
  * Compute derived connection options after absorbing all user-supplied info.
@@ -1396,19 +1453,36 @@ 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 (!validateAndGetTargetServerTypeFromSessionAttrs(conn->target_session_attrs, &conn->requested_server_type))
 		{
 			conn->status = CONNECTION_BAD;
 			printfPQExpBuffer(&conn->errorMessage,
 							  libpq_gettext("invalid %s value: \"%s\"\n"),
-							  "target_settion_attrs",
+							  "target_session_attrs",
 							  conn->target_session_attrs);
 			return false;
 		}
 	}
 
 	/*
+	 * 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 (!validateAndGetTargetServerType(conn->target_server_type, &conn->requested_server_type))
+		{
+			conn->status = CONNECTION_BAD;
+			printfPQExpBuffer(&conn->errorMessage,
+							  libpq_gettext("invalid %s value: \"%s\"\n"),
+							  "target_server_type",
+							  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 +2302,116 @@ 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 (read-write/read-only).
+ * 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_primary_or_rw_host == -1)
+	{
+		/*
+		 * This can only happen if server version < 14 (for which standby
+		 * is regarded as read-only)
+		 */
+		conn->which_primary_or_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 primary 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 primary host index */
+	if (conn->requested_server_type == SERVER_TYPE_PREFER_STANDBY && conn->which_primary_or_rw_host == -1)
+		conn->which_primary_or_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
  *
@@ -2346,13 +2530,34 @@ 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_primary_or_rw_host >= 0)
+			{
+				/*
+				 * Getting here means we failed to connect to standby servers
+				 * (or to read-only servers for server verson < 14) and should
+				 * now try to re-connect to a previously-connected-to primary
+				 * server (or read-write server for server version < 14),
+				 * whose host index is recorded in which_primary_or_rw_host.
+				 */
+				conn->whichhost = conn->which_primary_or_rw_host;
+
+				/*
+				 * Reset the host index value to avoid recursion during the
+				 * second connection attempt.
+				 */
+				conn->which_primary_or_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;
+			}
 		}
-		conn->whichhost++;
+		else
+			conn->whichhost++;
 
 		/* Drop any address info for previous host */
 		release_conn_addrinfo(conn);
@@ -3560,38 +3765,169 @@ 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.
-				 *
 				 * 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)
 				{
 					/*
-					 * 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 a read-write or read-only connection is required,
+					 * see if we have one.
 					 */
-					if (!saveErrorMessage(conn, &savedMessage))
-						goto error_return;
+					if (conn->requested_server_type == SERVER_TYPE_READ_WRITE ||
+						conn->requested_server_type == SERVER_TYPE_READ_ONLY)
+					{
+						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->transaction_read_only &&
+								  conn->requested_server_type == SERVER_TYPE_READ_WRITE) ||
+								 (!conn->transaction_read_only &&
+								  conn->requested_server_type == SERVER_TYPE_READ_ONLY))
+						{
+							/*
+							 * Server is read-only but requested read-write,
+							 * or server is read-write but requested
+							 * read-only, reject and continue to process any
+							 * further hosts ...
+							 */
+
+							rejectCheckedReadOrWriteConnection(conn);
+							goto keep_going;
+						}
+
+						/* obtained the requested type, consume it */
+						goto consume_checked_target_connection;
+					}
+					else if (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)))
+						{
+							/*
+							 * Server is in recovery but requested primary, or
+							 * server is not in recovery but requested
+							 * prefer-standby/standby.
+							 */
+
+							if (conn->which_primary_or_rw_host == -2)
+							{
+								/*
+								 * This 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.
+								 */
+								goto consume_checked_target_connection;
+							}
+
+							/*
+							 * Reject and continue to process any further
+							 * hosts ...
+							 */
 
+							rejectCheckedRecoveryConnection(conn);
+							goto keep_going;
+						}
+
+						/* obtained the requested type, consume it */
+						goto consume_checked_target_connection;
+					}
+				}
+
+				/*
+				 * For servers before 7.4 (which don't support read-only), 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 read-only or standby, ignore this connection.
+				 */
+
+				if (conn->requested_server_type == SERVER_TYPE_PREFER_STANDBY ||
+					conn->requested_server_type == SERVER_TYPE_READ_ONLY ||
+					conn->requested_server_type == SERVER_TYPE_STANDBY)
+				{
+					if (conn->which_primary_or_rw_host == -2)
+					{
+						/*
+						 * This 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 read-only.
+						 */
+						goto consume_checked_target_connection;
+					}
+
+					/* Close connection politely. */
 					conn->status = CONNECTION_OK;
-					if (!PQsendQuery(conn,
-									 "SHOW transaction_read_only"))
+					sendTerminateConn(conn);
+
+					/* Record host index */
+					if (conn->requested_server_type == SERVER_TYPE_PREFER_STANDBY)
 					{
-						restoreErrorMessage(conn, &savedMessage);
-						goto error_return;
+						if (conn->which_primary_or_rw_host == -1)
+							conn->which_primary_or_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);
 
@@ -3663,6 +3999,7 @@ keep_going:						/* We will come back to here until there is
 				conn->status = CONNECTION_OK;
 				return PGRES_POLLING_OK;
 			}
+
 		case CONNECTION_CHECK_WRITABLE:
 			{
 				const char *displayed_host;
@@ -3690,42 +4027,51 @@ 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
+					 * read-write (or requested server type is primary and
+					 * server version < 14), ignore this connection. Server is
+					 * read-write and requested type is read-only (or
+					 * requested server type is standby and server version <
+					 * 14), ignore this connection. Server is read-write
+					 * (version < 14) and requested type is prefer-standby,
+					 * record it for the first time and try to consume in the
+					 * next scan (it means no read-only server is found in the
+					 * first scan).
+					 */
+					if ((readonly_server &&
+						 (conn->requested_server_type == SERVER_TYPE_READ_WRITE ||
+						  conn->requested_server_type == SERVER_TYPE_PRIMARY)) ||
+						(!readonly_server &&
+						 (conn->requested_server_type == SERVER_TYPE_PREFER_STANDBY ||
+						  conn->requested_server_type == SERVER_TYPE_READ_ONLY ||
+						  conn->requested_server_type == SERVER_TYPE_STANDBY)))
 					{
-						/* Not writable; fail this connection. */
+						if (conn->which_primary_or_rw_host == -2)
+						{
+							/*
+							 * This 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 read-only.
+							 */
+							goto consume_checked_target_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;
-
-						appendPQExpBuffer(&conn->errorMessage,
-										  libpq_gettext("could not make a writable "
-														"connection to server "
-														"\"%s:%s\"\n"),
-										  displayed_host, displayed_port);
-
-						/* Close connection politely. */
-						conn->status = CONNECTION_OK;
-						sendTerminateConn(conn);
-
-						/*
-						 * Try next host if any, but we don't want to consider
-						 * additional addresses for this host.
-						 */
-						conn->try_next_host = true;
+						rejectCheckedReadOrWriteConnection(conn);
 						goto keep_going;
 					}
 
-					/* Session is read-write, so we're good. */
+					/* Session is requested type, so we're good. */
 					PQclear(res);
 					termPQExpBuffer(&savedMessage);
 
@@ -3903,10 +4249,14 @@ makeEmptyPGconn(void)
 	conn->setenv_state = SETENV_STATE_IDLE;
 	conn->client_encoding = PG_SQL_ASCII;
 	conn->std_strings = false;	/* unless server says differently */
+	conn->transaction_read_only = false;
 	conn->verbosity = PQERRORS_DEFAULT;
 	conn->show_context = PQSHOW_CONTEXT_ERRORS;
 	conn->sock = PGINVALID_SOCKET;
 
+	conn->requested_server_type = SERVER_TYPE_ANY;
+	conn->which_primary_or_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
@@ -4075,6 +4425,8 @@ freePGconn(PGconn *conn)
 		free(conn->rowBuf);
 	if (conn->target_session_attrs)
 		free(conn->target_session_attrs);
+	if (conn->target_server_type)
+		free(conn->target_server_type);
 	termPQExpBuffer(&conn->errorMessage);
 	termPQExpBuffer(&conn->workBuffer);
 
diff --git a/src/interfaces/libpq/fe-exec.c b/src/interfaces/libpq/fe-exec.c
index eea0237..0605b23 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, transaction_read_only 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,14 @@ pqSaveParameterStatus(PGconn *conn, const char *name, const char *value)
 		else
 			conn->sversion = 0; /* unknown */
 	}
+	else if (strcmp(name, "transaction_read_only") == 0)
+	{
+		conn->transaction_read_only = (strcmp(value, "on") == 0);
+	}
+	else if (strcmp(name, "in_recovery") == 0)
+	{
+		conn->in_recovery = (strcmp(value, "on") == 0);
+	}
 }
 
 
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 1de91ae..13376ee 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -317,6 +317,17 @@ 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_READ_WRITE,		/* Read-write server */
+	SERVER_TYPE_READ_ONLY,		/* Read-only server */
+	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 +381,30 @@ 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", "read-write",
+	 * "read-only", "primary", "prefer-standby" (or "prefer-secondary"),
+	 * "standby" (or "secondary").
+	 */
 	char	   *target_session_attrs;
 
+	/*
+	 * Type of server to connect to. Possible values: "any", "primary",
+	 * "prefer-secondary", "secondary" This overrides any connection type
+	 * specified by target_session_attrs. This option supports a subset of the
+	 * target_session_attrs option values, and 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 +438,24 @@ struct pg_conn
 	pg_conn_host *connhost;		/* details about each named host */
 	char	   *connip;			/* IP address for current network connection */
 
+	/*
+	 * Index of the first primary host (or read-write host if server version
+	 * < 14) encountered (if any) in the connection string. This is used
+	 * during processing of requested server connection type
+	 * SERVER_TYPE_PREFER_STANDBY.
+	 *
+	 * The initial value is -1, indicating that no primary host has yet been
+	 * found. It is then set to the index of the first primary host, if one is
+	 * found in the connection string during processing. If a second
+	 * connection attempt is later made to that primary host (because no
+	 * connection to a standby server could be made), which_primary_or_rw_host
+	 * is then set to -2 to avoid recursion during subsequent processing (and
+	 * whichhost is set to the primary host index). Note that for server
+	 * versions < 14, a requested type of "primary" is regarded as
+	 * "read-write" and "standby" is regarded as "read-only".
+	 */
+	int		which_primary_or_rw_host;
+
 	/* Connection data */
 	pgsocket	sock;			/* FD for socket, PGINVALID_SOCKET if
 								 * unconnected */
@@ -436,6 +486,8 @@ struct pg_conn
 	pgParameterStatus *pstatus; /* ParameterStatus data */
 	int			client_encoding;	/* encoding id */
 	bool		std_strings;	/* standard_conforming_strings */
+	bool		transaction_read_only;	/* transaction_read_only */
+	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 +592,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..a1e59c4 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 => 66;
 
 # Initialize primary node
 my $node_primary = get_new_node('primary');
@@ -85,7 +85,7 @@ sub test_target_session_attrs
 	my $node2_port = $node2->port;
 	my $node2_name = $node2->name;
 
-	my $target_name = $target_node->name;
+	my $target_name = $target_node->name if (defined $target_node);
 
 	# Build connection string for connection attempt.
 	my $connstr = "host=$node1_host,$node2_host ";
@@ -97,10 +97,25 @@ sub test_target_session_attrs
 	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"
-	);
+	if ($status == 0)
+	{
+		is( $status == $ret && $stdout eq $target_node->port,
+			1,
+			"connect to node $target_name if mode \"$mode\" and $node1_name,$node2_name listed"
+		);
+	}
+	else
+	{
+		print "status = $status\n";
+		print "ret = $ret\n";
+		print "stdout = $stdout\n";
+		print "stderr = $stderr\n";
+
+		is( $status == $ret,
+			1,
+			"fail to connect to any nodes if mode \"$mode\" and $node1_name,$node2_name listed"
+		);
+	}
 
 	return;
 }
@@ -121,6 +136,181 @@ 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 standby1 in "read-only" mode with primary,standby1 list.
+test_target_session_attrs($node_primary, $node_standby_1, $node_standby_1,
+	"read-only", 0);
+
+# Connect to standby1 in "read-only" mode with standby1,primary list.
+test_target_session_attrs($node_standby_1, $node_primary, $node_standby_1,
+	"read-only", 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);
+
+# Fail to connect in "read-write" mode with standby1,standby2 list.
+test_target_session_attrs($node_standby_1, $node_standby_2, undef,
+	"read-write", 2);
+
+# Fail to connect in "primary" mode with standby1,standby2 list.
+test_target_session_attrs($node_standby_1, $node_standby_2, undef,
+	"primary", 2);
+
+# Fail to connect in "read-only" mode with primary,primary list.
+test_target_session_attrs($node_primary, $node_primary, undef,
+	"read-only", 2);
+
+# Fail to connect in "standby" mode with primary,primary list.
+test_target_session_attrs($node_primary, $node_primary, undef,
+	"standby", 2);
+
+# Fail to connect in "secondary" mode with primary,primary list.
+test_target_session_attrs($node_primary, $node_primary, undef,
+	"secondary", 2);
+
+# 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 if (defined $target_node);
+
+	# 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 ]);
+	if ($status == 0)
+	{
+		is( $status == $ret && $stdout eq $target_node->port,
+			1,
+			"connect to node $target_name if mode \"$mode\" and $node1_name,$node2_name listed"
+		);
+	}
+	else
+	{
+		print "status = $status\n";
+		print "ret = $ret\n";
+		print "stdout = $stdout\n";
+		print "stderr = $stderr\n";
+
+		is( $status == $ret,
+			1,
+			"fail to connect to any nodes if mode \"$mode\" and $node1_name,$node2_name listed"
+		);
+	}
+
+	return;
+}
+
+# Connect to primary in "any" mode with primary,standby1 list.
+test_target_server_type($node_primary, $node_standby_1, $node_primary, "any",
+	0);
+
+# Connect to standby1 in "any" mode with standby1,primary list.
+test_target_server_type($node_standby_1, $node_primary, $node_standby_1,
+	"any", 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-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 "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);
+
+# Fail to connect in "primary" mode with standby1,standby2 list.
+test_target_server_type($node_standby_1, $node_standby_2, undef,
+	"primary", 2);
+
+# Fail to connect in "secondary" mode with primary,primary list.
+test_target_server_type($node_primary, $node_primary, undef,
+	"secondary", 2);
+
 # Test for SHOW commands using a WAL sender connection with a replication
 # role.
 note "testing SHOW commands for replication connection";
-- 
1.8.3.1

