On Tue, Feb 9, 2021 at 5:47 AM Greg Nancarrow <gregn4...@gmail.com> wrote:
>
> On Mon, Feb 8, 2021 at 8:17 PM vignesh C <vignes...@gmail.com> wrote:
> >
> > >
> > > I think what we want to do is mark default_transaction_read_only as
> > > GUC_REPORT, instead.  That will give a reliable report of what the
> > > state of its GUC is, and you can combine it with is_hot_standby
> > > to decide whether the session should be considered read-only.
> > > If you don't get those two GUC values during connection, then you
> > > can fall back on "SHOW transaction_read_only".
> > >
> >
> > I have made a patch for the above with the changes suggested and
> > rebased it with the head code.
> > Attached v21 patch which has the changes for the same.
> > Thoughts?
> >
>
> I'm still looking at the patch code, but I noticed that the
> documentation update describing how support of read-write transactions
> is determined isn't quite right and it isn't clear how the parameters
> work.
> I'd suggest something like the following (you'd need to fix the line
> lengths and line-wrapping appropriately) - please check it for
> correctness:
>
>        <para>
>         The support of read-write transactions is determined by the value of 
> the
>         <varname>default_transaction_read_only</varname> and
>         <varname>in_hot_standby</varname> configuration parameters,
> that, if supported,
>         are reported by the server upon successful connection. If the
> value of either
>         of these parameters is <literal>on</literal>, it means the
> server doesn't support
>         read-write transactions. If either/both of these parameters
> are not reported,
>         then the support of read-write transactions is determined by
> an explicit query,
>         by sending <literal>SHOW transaction_read_only</literal> after
> successful
>         connection; if it returns <literal>on</literal>, it means the
> server doesn't
>         support read-write transactions. The standby mode state is
> determined by either
>         the value of the <varname>in_hot_standby</varname> configuration
>         parameter, that is reported by the server (if supported) upon
>         successful connection, or is otherwise explicitly queried by sending
>         <literal>SELECT pg_is_in_recovery()</literal> after successful
>         connection; if it returns <literal>t</literal>, it means the server is
>         in hot standby mode.
>        </para>

Thanks Greg for the comments, Please find the attached v22 patch
having the fix for the same.
Thoughts?

Regards,
Vignesh
From db9b894d8a8c51de80e6b3b7b8c660dda5374ff9 Mon Sep 17 00:00:00 2001
From: Vignesh C <vignes...@gmail.com>
Date: Mon, 8 Feb 2021 11:23:31 +0530
Subject: [PATCH v22] Enhance the libpq "target_session_attrs" connection
 parameter.

Enhance the connection parameter "target_session_attrs" to support new values:
read-only/primary/standby/prefer-standby.
Add a new "read-only" target_session_attrs option value, to support connecting
to a read-only server if available from the list of hosts (otherwise the
connection attempt fails).
Add a new "primary" target_session_attrs option value, to support connecting to
a server which is not in hot-standby mode, if available from the list of hosts
(otherwise the connection attempt fails).
Add a new "standby" target_session_attrs option value, to support connecting to
a server which is in hot-standby mode, if available from the list of hosts
(otherwise the connection attempt fails).
Add a new "prefer-standby" target_session_attrs option value, to support
connecting to a server which is in hot-standby mode, if available from the list
of hosts (otherwise connect to a server which is not in hot-standby mode).

Discussion: https://www.postgresql.org/message-id/flat/caf3+xm+8-ztokav9ghij3wfgentq97qcjxqt+rbfq6f7onz...@mail.gmail.com
---
 doc/src/sgml/high-availability.sgml   |  16 +-
 doc/src/sgml/libpq.sgml               |  73 +++++-
 doc/src/sgml/protocol.sgml            |   5 +-
 src/backend/utils/misc/guc.c          |   3 +-
 src/interfaces/libpq/fe-connect.c     | 432 ++++++++++++++++++++++++++++++----
 src/interfaces/libpq/fe-exec.c        |  18 +-
 src/interfaces/libpq/libpq-fe.h       |   3 +-
 src/interfaces/libpq/libpq-int.h      |  52 +++-
 src/test/recovery/t/001_stream_rep.pl |  79 ++++++-
 src/tools/pgindent/typedefs.list      |   2 +
 10 files changed, 602 insertions(+), 81 deletions(-)

diff --git a/doc/src/sgml/high-availability.sgml b/doc/src/sgml/high-availability.sgml
index f49f5c0..2bbd52c 100644
--- a/doc/src/sgml/high-availability.sgml
+++ b/doc/src/sgml/high-availability.sgml
@@ -1700,14 +1700,14 @@ synchronous_standby_names = 'ANY 2 (s1, s2, s3)'
    </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
-    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
-    server changes mode.  Once hot standby finishes, it will be possible to
-    initiate read-write transactions (even from a session begun during
-    hot standby).
+    During hot standby, the parameter <varname>in_hot_standby</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 server
+    changes mode.  Once hot standby finishes, it will be possible to initiate
+    read-write transactions (even from a session begun during hot standby).
    </para>
 
    <para>
diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index b7a8245..d5f0f24 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -1836,18 +1836,66 @@ 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>.
+        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>
+        The support of read-write transactions is determined by the value of the
+        <varname>default_transaction_read_only</varname> and
+        <varname>in_hot_standby</varname> configuration parameters, that is
+        reported by the server (if supported) upon successful connection.  If
+        the value of either of these parameters is <literal>on</literal>, it
+        means the server doesn't support read-write transactions. If
+        either/both of these parameters are not reported, then the support of
+        read-write transactions is determined by an explicit query, by sending
+        <literal>SHOW transaction_read_only</literal> after successful
+        connection; if it returns <literal>on</literal>, it means the server
+        doesn't support read-write transactions. The standby mode state is
+        determined by either the value of the <varname>in_hot_standby</varname>
+        configuration parameter, that is reported by the server (if supported)
+        upon successful connection, or is otherwise explicitly queried by
+        sending <literal>SELECT pg_is_in_recovery()</literal> after successful
+        connection; if it returns <literal>t</literal>, it means the server is
+        in hot standby mode.
+       </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.
+       </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 only a connection in
+        which the server is not in hot standby mode is considered acceptable.
+       </para>
+
+       <para>
+        If this parameter is set to <literal>standby</literal>, then only a connection in
+        which the server is in hot standby mode is considered acceptable.
+       </para>
+
+       <para>
+        If this parameter is set to <literal>prefer-standby</literal>, then a connection
+        in which the server is in hot standby mode is preferred. Otherwise, if no such
+        connections can be found, then a connection in which the server is not in hot
+        standby mode will be considered.
+       </para>
+
       </listitem>
-    </varlistentry>
+     </varlistentry>
+
     </variablelist>
    </para>
   </sect2>
@@ -2150,6 +2198,7 @@ const char *PQparameterStatus(const PGconn *conn, const char *paramName);
        <varname>server_encoding</varname>,
        <varname>client_encoding</varname>,
        <varname>application_name</varname>,
+       <varname>default_transaction_read_only</varname>,
        <varname>in_hot_standby</varname>,
        <varname>is_superuser</varname>,
        <varname>session_authorization</varname>,
@@ -2165,6 +2214,7 @@ const char *PQparameterStatus(const PGconn *conn, const char *paramName);
        <varname>IntervalStyle</varname> was not reported by releases before 8.4;
        <varname>application_name</varname> was not reported by releases before
        9.0;
+       <varname>default_transaction_read_only</varname>  and
        <varname>in_hot_standby</varname> was not reported by releases before
        14.)
        Note that
@@ -7268,6 +7318,7 @@ myEventProc(PGEventId evtId, void *evtInfo, void *passThrough)
       linkend="libpq-connect-target-session-attrs"/> connection parameter.
      </para>
     </listitem>
+
    </itemizedlist>
   </para>
 
diff --git a/doc/src/sgml/protocol.sgml b/doc/src/sgml/protocol.sgml
index 3763b4b..57d1f2b 100644
--- a/doc/src/sgml/protocol.sgml
+++ b/doc/src/sgml/protocol.sgml
@@ -1278,6 +1278,7 @@ SELCT 1/0;<!-- this typo is intentional -->
     <varname>server_encoding</varname>,
     <varname>client_encoding</varname>,
     <varname>application_name</varname>,
+    <varname>default_transaction_read_only</varname>,
     <varname>in_hot_standby</varname>,
     <varname>is_superuser</varname>,
     <varname>session_authorization</varname>,
@@ -1293,8 +1294,8 @@ SELCT 1/0;<!-- this typo is intentional -->
     <varname>IntervalStyle</varname> was not reported by releases before 8.4;
     <varname>application_name</varname> was not reported by releases before
     9.0;
-    <varname>in_hot_standby</varname> was not reported by releases before
-    14.)
+    <varname>default_transaction_read_only</varname> and
+    <varname>in_hot_standby</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/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index eafdb11..3dba213 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -1619,7 +1619,8 @@ static struct config_bool ConfigureNamesBool[] =
 	{
 		{"default_transaction_read_only", PGC_USERSET, CLIENT_CONN_STATEMENT,
 			gettext_noop("Sets the default read-only status of new transactions."),
-			NULL
+			NULL,
+			GUC_REPORT
 		},
 		&DefaultXactReadOnly,
 		false,
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index 8ca0583..e389a6d 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -352,7 +352,7 @@ 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)},
 
 	/* Terminating entry --- MUST BE LAST */
@@ -1006,6 +1006,33 @@ parse_comma_separated_list(char **startptr, bool *more)
 }
 
 /*
+ *		validateAndGetTargetServerType
+ *
+ * Validate a given target_session_attrs 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, "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.
@@ -1401,13 +1428,12 @@ 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 (!validateAndGetTargetServerType(conn->target_session_attrs, &conn->requested_server_type))
 		{
 			conn->status = CONNECTION_BAD;
 			appendPQExpBuffer(&conn->errorMessage,
 							  libpq_gettext("invalid %s value: \"%s\"\n"),
-							  "target_settion_attrs",
+							  "target_session_attrs",
 							  conn->target_session_attrs);
 			return false;
 		}
@@ -2192,6 +2218,68 @@ connectDBComplete(PGconn *conn)
 	}
 }
 
+/*
+ * 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).
+ */
+static void
+rejectCheckedReadOrWriteConnection(PGconn *conn)
+{
+	if (conn->requested_server_type == SERVER_TYPE_READ_WRITE)
+		appendPQExpBufferStr(&conn->errorMessage,
+							 libpq_gettext("could not make a writable "
+										   "connection to server\n"));
+	else
+		appendPQExpBufferStr(&conn->errorMessage,
+							 libpq_gettext("could not make a readonly "
+										   "connection to server\n"));
+
+	/* 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;
+}
+
+/*
+ * Internal helper function used for rejecting (and closing) a connection that
+ * doesn't satisfy the requested server type (for standby). 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
+rejectCheckedStandbyConnection(PGconn *conn)
+{
+	if (conn->requested_server_type == SERVER_TYPE_PRIMARY)
+		appendPQExpBufferStr(&conn->errorMessage,
+							 libpq_gettext("server is in hot standby mode\n"));
+	else
+		appendPQExpBufferStr(&conn->errorMessage,
+							 libpq_gettext("server is not in hot standby mode\n"));
+
+	/* 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_host == -1)
+		conn->which_primary_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
  *
@@ -2273,6 +2361,7 @@ PQconnectPoll(PGconn *conn)
 		case CONNECTION_CHECK_WRITABLE:
 		case CONNECTION_CONSUME:
 		case CONNECTION_GSS_STARTUP:
+		case CONNECTION_CHECK_STANDBY:
 			break;
 
 		default:
@@ -2309,13 +2398,33 @@ 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_host >= 0)
+			{
+				/*
+				 * Getting here means we failed to connect to standby servers
+				 * and should now try to re-connect to a
+				 * previously-connected-to primary server, whose host index is
+				 * recorded in which_primary_host.
+				 */
+				conn->whichhost = conn->which_primary_host;
+
+				/*
+				 * Reset the host index value to avoid recursion during the
+				 * second connection attempt.
+				 */
+				conn->which_primary_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);
@@ -3545,31 +3654,175 @@ 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->requested_server_type != SERVER_TYPE_ANY)
 				{
 					/*
-					 * We use PQsendQueryContinue so that conn->errorMessage
-					 * does not get cleared.  We need to preserve any 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.
+					 *
+					 * Servers before 7.4 lack the
+					 * default_transaction_read_only & in_hot_standby GUC, but
+					 * by the same token they don't have any read-only mode,
+					 * so we may just skip the test in that case.
 					 */
-					conn->status = CONNECTION_OK;
-					if (!PQsendQueryContinue(conn,
-											 "SHOW transaction_read_only"))
-						goto error_return;
-					conn->status = CONNECTION_CHECK_WRITABLE;
-					return PGRES_POLLING_READING;
+
+					if (conn->sversion >= 70400 &&
+						(conn->requested_server_type == SERVER_TYPE_READ_WRITE ||
+						 conn->requested_server_type == SERVER_TYPE_READ_ONLY))
+					{
+						/*
+						 * For servers which don't have
+						 * "default_transaction_read_only" or "in_hot_standby"
+						 * as a GUC_REPORT variable, it in necessary to
+						 * determine if they are read-only by sending the
+						 * query "SHOW transaction_read_only".
+						 */
+						if (conn->default_transaction_read_only == GUC_BOOL_UNKNOWN ||
+							conn->in_hot_standby == GUC_BOOL_UNKNOWN)
+						{
+							/*
+							 * We use PQsendQueryContinue so that
+							 * conn->errorMessage does not get cleared.  We
+							 * need to preserve any error messages related to
+							 * previous hosts we have tried and failed to
+							 * connect to.
+							 */
+							conn->status = CONNECTION_OK;
+							if (!PQsendQueryContinue(conn, "SHOW transaction_read_only"))
+								goto error_return;
+
+							conn->status = CONNECTION_CHECK_WRITABLE;
+							return PGRES_POLLING_READING;
+						}
+						else if (((conn->default_transaction_read_only == GUC_BOOL_YES ||
+								   conn->in_hot_standby == GUC_BOOL_YES) &&
+								  conn->requested_server_type == SERVER_TYPE_READ_WRITE) ||
+								 (conn->default_transaction_read_only == GUC_BOOL_NO &&
+								  conn->in_hot_standby == GUC_BOOL_NO &&
+								  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;
+					}
+
+					/*
+					 * Servers before 9.0 don't support standby mode, skip the
+					 * check when the requested type of connection is primary,
+					 * prefer-standby or standby.
+					 */
+					else if ((conn->sversion >= 90000 &&
+							  (conn->requested_server_type == SERVER_TYPE_PRIMARY ||
+							   conn->requested_server_type == SERVER_TYPE_PREFER_STANDBY ||
+							   conn->requested_server_type == SERVER_TYPE_STANDBY)))
+					{
+						/*
+						 * For servers which don't have the "in_hot_standby"
+						 * GUC_REPORT variable, it in necessary to determine
+						 * if they are in hot standby mode by sending the
+						 * query "SELECT pg_is_in_recovery()".
+						 */
+						if (conn->in_hot_standby == GUC_BOOL_UNKNOWN)
+						{
+							/*
+							 * We use PQsendQueryContinue so that
+							 * conn->errorMessage does not get cleared.  We
+							 * need to preserve any error messages related to
+							 * previous hosts we have tried and failed to
+							 * connect to.
+							 */
+							conn->status = CONNECTION_OK;
+							if (!PQsendQueryContinue(conn, "SELECT pg_is_in_recovery()"))
+								goto error_return;
+
+							conn->status = CONNECTION_CHECK_STANDBY;
+							return PGRES_POLLING_READING;
+						}
+						else if ((conn->in_hot_standby == GUC_BOOL_YES &&
+								  conn->requested_server_type == SERVER_TYPE_PRIMARY) ||
+								 (conn->in_hot_standby == GUC_BOOL_NO &&
+								  (conn->requested_server_type == SERVER_TYPE_PREFER_STANDBY ||
+								   conn->requested_server_type == SERVER_TYPE_STANDBY)))
+						{
+							/*
+							 * Server is in standby but requested primary, or
+							 * server is not in standby but requested
+							 * prefer-standby/standby, reject and continue to
+							 * process any further hosts ...
+							 */
+							if (conn->which_primary_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 standby mode.
+								 */
+								goto consume_checked_target_connection;
+							}
+
+							rejectCheckedStandbyConnection(conn);
+							goto keep_going;
+						}
+
+						/* obtained the requested type, consume it */
+						goto consume_checked_target_connection;
+					}
+
+					/*
+					 * If the requested type is prefer-standby, then record
+					 * this host index and try any others before considering
+					 * it later. If the requested type of connection is
+					 * read-only or standby, ignore this connection, as
+					 * servers of this version don't support this type of
+					 * 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_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 standby mode.
+							 */
+							goto consume_checked_target_connection;
+						}
+
+						/* Close connection politely. */
+						conn->status = CONNECTION_OK;
+						sendTerminateConn(conn);
+
+						/* Record host index */
+						if (conn->requested_server_type == SERVER_TYPE_PREFER_STANDBY)
+						{
+							if (conn->which_primary_host == -1)
+								conn->which_primary_host = conn->whichhost;
+						}
+
+						/*
+						 * Try the 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);
 
@@ -3641,6 +3894,7 @@ keep_going:						/* We will come back to here until there is
 				conn->status = CONNECTION_OK;
 				return PGRES_POLLING_OK;
 			}
+
 		case CONNECTION_CHECK_WRITABLE:
 			{
 				conn->status = CONNECTION_OK;
@@ -3658,30 +3912,112 @@ 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, ignore this connection. Server is
+					 * read-write and requested type is read-only, ignore this
+					 * connection.
+					 */
+					if ((readonly_server &&
+						 (conn->requested_server_type == SERVER_TYPE_READ_WRITE)) ||
+						(!readonly_server &&
+						 (conn->requested_server_type == SERVER_TYPE_READ_ONLY)))
 					{
-						/* Not writable; fail this connection. */
+						/* Not a requested type; fail this connection. */
 						PQclear(res);
+						rejectCheckedReadOrWriteConnection(conn);
+						goto keep_going;
+					}
+					/* Session is requested type, so we're good. */
+					PQclear(res);
 
-						/* Append error report to conn->errorMessage. */
-						appendPQExpBufferStr(&conn->errorMessage,
-											 libpq_gettext("session is read-only\n"));
+					/*
+					 * Finish reading any remaining messages before being
+					 * considered as ready.
+					 */
+					conn->status = CONNECTION_CONSUME;
+					goto keep_going;
+				}
 
-						/* Close connection politely. */
-						conn->status = CONNECTION_OK;
-						sendTerminateConn(conn);
+				/*
+				 * Something went wrong with "SHOW transaction_read_only". We
+				 * should try next addresses.
+				 */
+				if (res)
+					PQclear(res);
 
+				/* Append error report to conn->errorMessage. */
+				appendPQExpBufferStr(&conn->errorMessage,
+									 libpq_gettext("test \"SHOW transaction_read_only\" failed\n"));
+
+				/* Close connection politely. */
+				conn->status = CONNECTION_OK;
+				sendTerminateConn(conn);
+
+				/* Try next address */
+				conn->try_next_addr = true;
+				goto keep_going;
+			}
+
+		case CONNECTION_CHECK_STANDBY:
+			{
+				conn->status = CONNECTION_OK;
+				if (!PQconsumeInput(conn))
+				{
+					goto error_return;
+				}
+
+				if (PQisBusy(conn))
+				{
+					conn->status = CONNECTION_CHECK_STANDBY;
+					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 standby mode and requested mode is
+					 * primary, ignore it. Server is not in standby mode and
+					 * requested mode 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 mode for the next pass of the list
+						 * of connections, as it couldn't find any servers
+						 * that are in standby mode.
 						 */
-						conn->try_next_host = true;
+						if (conn->which_primary_host == -2)
+							goto consume_checked_standby_connection;
+
+						/* Not a requested type; fail this connection. */
+						PQclear(res);
+
+						rejectCheckedStandbyConnection(conn);
 						goto keep_going;
 					}
-
-					/* Session is read-write, so we're good. */
+			consume_checked_standby_connection:
+					/* Session is requested type, so we're good. */
 					PQclear(res);
 
 					/*
@@ -3693,7 +4029,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)
@@ -3701,7 +4037,7 @@ keep_going:						/* We will come back to here until there is
 
 				/* Append error report to conn->errorMessage. */
 				appendPQExpBufferStr(&conn->errorMessage,
-									 libpq_gettext("test \"SHOW transaction_read_only\" failed\n"));
+									 libpq_gettext("test \"SELECT pg_is_in_recovery()\" failed\n"));
 
 				/* Close connection politely. */
 				conn->status = CONNECTION_OK;
@@ -3711,7 +4047,6 @@ keep_going:						/* We will come back to here until there is
 				conn->try_next_addr = true;
 				goto keep_going;
 			}
-
 		default:
 			appendPQExpBuffer(&conn->errorMessage,
 							  libpq_gettext("invalid connection state %d, "
@@ -3855,10 +4190,15 @@ makeEmptyPGconn(void)
 	conn->setenv_state = SETENV_STATE_IDLE;
 	conn->client_encoding = PG_SQL_ASCII;
 	conn->std_strings = false;	/* unless server says differently */
+	conn->default_transaction_read_only = GUC_BOOL_UNKNOWN;
+	conn->in_hot_standby = GUC_BOOL_UNKNOWN;
 	conn->verbosity = PQERRORS_DEFAULT;
 	conn->show_context = PQSHOW_CONTEXT_ERRORS;
 	conn->sock = PGINVALID_SOCKET;
 
+	conn->requested_server_type = SERVER_TYPE_ANY;
+	conn->which_primary_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 e730753..60cd4a7 100644
--- a/src/interfaces/libpq/fe-exec.c
+++ b/src/interfaces/libpq/fe-exec.c
@@ -1008,11 +1008,11 @@ pqSaveParameterStatus(PGconn *conn, const char *name, const char *value)
 	}
 
 	/*
-	 * 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
-	 * least in single-connection-using programs).
+	 * Special hacks: remember client_encoding, default_transaction_read_only,
+	 * in_hot_standby 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 least in single-connection-using programs).
 	 */
 	if (strcmp(name, "client_encoding") == 0)
 	{
@@ -1062,6 +1062,14 @@ pqSaveParameterStatus(PGconn *conn, const char *name, const char *value)
 		else
 			conn->sversion = 0; /* unknown */
 	}
+	else if (strcmp(name, "default_transaction_read_only") == 0)
+	{
+		conn->default_transaction_read_only = (strcmp(value, "on") == 0 ? GUC_BOOL_YES : GUC_BOOL_NO);
+	}
+	else if (strcmp(name, "in_hot_standby") == 0)
+	{
+		conn->in_hot_standby = (strcmp(value, "on") == 0 ? GUC_BOOL_YES : GUC_BOOL_NO);
+	}
 }
 
 
diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h
index c266ad5..5c1b349 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_STANDBY	/* Check whether server is in standby mode */
 } ConnStatusType;
 
 typedef enum
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 4db4983..0ba81ae 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -317,6 +317,29 @@ 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;
+
+/*
+ * State of certain bool GUCs used by libpq, which are determined
+ * either by the GUC_REPORT mechanism (where supported by the server
+ * version) or by lazy evaluation (using a query sent to the server).
+ */
+typedef enum
+{
+	GUC_BOOL_UNKNOWN = 0,		/* Currently unknown	*/
+	GUC_BOOL_YES,				/* Yes (true)			*/
+	GUC_BOOL_NO					/* No (false)			*/
+} GucBoolState;
+
 /*
  * PGconn stores all the state data associated with a single connection
  * to a backend.
@@ -370,9 +393,17 @@ 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", "standby".
+	 */
 	char	   *target_session_attrs;
 
+	/*
+	 * The requested server type, derived from target_session_attrs.
+	 */
+	TargetServerType requested_server_type;
+
 	/* Optional file to write trace info to */
 	FILE	   *Pfdebug;
 
@@ -406,6 +437,21 @@ 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 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_host is
+	 * then set to -2 to avoid recursion during subsequent processing (and
+	 * whichhost is set to the primary host index).
+	 */
+	int			which_primary_host;
+
 	/* Connection data */
 	pgsocket	sock;			/* FD for socket, PGINVALID_SOCKET if
 								 * unconnected */
@@ -436,6 +482,10 @@ struct pg_conn
 	pgParameterStatus *pstatus; /* ParameterStatus data */
 	int			client_encoding;	/* encoding id */
 	bool		std_strings;	/* standard_conforming_strings */
+	GucBoolState default_transaction_read_only; /* default_transaction_read_only
+												 * GUC report variable state */
+	GucBoolState in_hot_standby;	/* in_hot_standby GUC report variable
+									 * state */
 	PGVerbosity verbosity;		/* error/notice message verbosity */
 	PGContextVisibility show_context;	/* whether to show CONTEXT field */
 	PGlobjfuncs *lobjfuncs;		/* private state for large-object access fns */
diff --git a/src/test/recovery/t/001_stream_rep.pl b/src/test/recovery/t/001_stream_rep.pl
index 9e31a53..15d0273 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 => 49;
 
 # 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,58 @@ 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 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);
+
+# 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);
+
 # Test for SHOW commands using a WAL sender connection with a replication
 # role.
 note "testing SHOW commands for replication connection";
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 1d540fe..4a9cc23 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -950,6 +950,7 @@ GroupingSetsPath
 GucAction
 GucBoolAssignHook
 GucBoolCheckHook
+GucBoolState
 GucContext
 GucEnumAssignHook
 GucEnumCheckHook
@@ -2512,6 +2513,7 @@ TapeShare
 TarMethodData
 TarMethodFile
 TargetEntry
+TargetServerType
 TclExceptionNameMap
 Tcl_DString
 Tcl_FileProc
-- 
1.8.3.1

Reply via email to