On Wed, Jan 6, 2021 at 3:05 AM Tom Lane <t...@sss.pgh.pa.us> wrote:
>
> Greg Nancarrow <gregn4...@gmail.com> writes:
> > Posting an updated set of patches.
>
> I've reviewed and pushed most of v20-0001, with the following changes:
>
> * I realized that we had more moving parts than necessary for
> in_hot_standby.  We don't really need two static variables, one is
> sufficient --- and we shouldn't make the SHOW hook have side-effects,
> that's just dangerous.
>
> * The documentation patches were missing an addition to config.sgml,
> as well as failing to list the new GUC in the two places where we
> document all GUC_REPORT variables.
>
> What I did *not* push was the change to mark transaction_read_only
> as GUC_REPORT.  I find that idea highly dubious, for a couple of
> reasons:
>
> * It'll create useless ParameterStatus traffic during normal operations
> of an application using "START TRANSACTION READ ONLY" or the like.
>
> * I do not think it will actually work for the desired purpose of
> finding out the read-only state during session connection.  AFAICS,
> we don't set XactReadOnly to a meaningful value except when starting
> a transaction.  Yeah, we'll run a transaction during login because
> we have to examine the system catalogs ... but do we start a new
> one after absorbing the effects of, say, ALTER USER SET
> default_transaction_read_only?  I doubt it, and even if it works
> today it'd be fragile, because someday somebody will try to collapse
> any multiple transactions during startup into one transaction.
>
> 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?

Regards,
Vignesh
From 9d85abfe1e4b43d67ee746891830abe53077c0e7 Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Mon, 8 Feb 2021 11:23:31 +0530
Subject: [PATCH v21] 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+rbFQ6F7oNzt9A@mail.gmail.com
---
 doc/src/sgml/high-availability.sgml   |  16 +-
 doc/src/sgml/libpq.sgml               |  70 +++++-
 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, 599 insertions(+), 81 deletions(-)

diff --git a/doc/src/sgml/high-availability.sgml b/doc/src/sgml/high-availability.sgml
index f49f5c0..a454e93 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>default_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..12bacda 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -1836,18 +1836,63 @@ 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 parameter, that is
+        either reported by the server (if supported) upon successful connection
+        or is otherwise explicitly queried 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 +2195,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 +2211,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 +7315,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..b24225c 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.)
     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..4e6aba8 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)
+		appendPQExpBuffer(&conn->errorMessage,
+						  libpq_gettext("could not make a writable "
+										"connection to server\n"));
+	else
+		appendPQExpBuffer(&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)
+		appendPQExpBuffer(&conn->errorMessage,
+						  libpq_gettext("server is in hot standby mode\n"));
+	else
+		appendPQExpBuffer(&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. */
+				appendPQExpBuffer(&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