From 463e566cfaa8d40a0d674fb4caab1b2cad05cad3 Mon Sep 17 00:00:00 2001
From: Hari Babu <kommi.haribabu@gmail.com>
Date: Wed, 27 Feb 2019 16:29:29 +1100
Subject: [PATCH 5/5] New read-only target_session_attrs type

With this read-only option type, application can connect to
connecting to a read-only server in the list of hosts, in case
if there is any read-only servers available, the connection
attempt fails.
---
 doc/src/sgml/libpq.sgml               |  2 +-
 src/interfaces/libpq/fe-connect.c     | 28 ++++++++++++++++++---------
 src/interfaces/libpq/libpq-fe.h       |  3 ++-
 src/interfaces/libpq/libpq-int.h      |  2 +-
 src/test/recovery/t/001_stream_rep.pl | 14 +++++++++++++-
 5 files changed, 36 insertions(+), 13 deletions(-)

diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index 7918e398c7..f7331fba32 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -1578,7 +1578,7 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
       <listitem>
        <para>
         The supported options for this parameter are, <literal>any</literal>, 
-        <literal>read-write</literal> and <literal>prefer-read</literal>.
+        <literal>read-write</literal>, <literal>prefer-read</literal> and <literal>read-only</literal>.
         The default value of this parameter, <literal>any</literal>, regards
         all connections as acceptable. If multiple hosts were specified in the
         connection string, based on the specified value, any remaining servers
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index 55bf9f1522..0f89af0787 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -1245,6 +1245,8 @@ connectOptions2(PGconn *conn)
 			conn->requested_session_type = SESSION_TYPE_READ_WRITE;
 		else if (strcmp(conn->target_session_attrs, "prefer-read") == 0)
 			conn->requested_session_type = SESSION_TYPE_PREFER_READ;
+		else if (strcmp(conn->target_session_attrs, "read-only") == 0)
+			conn->requested_session_type = SESSION_TYPE_READ_ONLY;
 		else
 		{
 			conn->status = CONNECTION_BAD;
@@ -3254,7 +3256,8 @@ keep_going:						/* We will come back to here until there is
 				 */
 				if (conn->sversion >= 70400 &&
 					(conn->requested_session_type == SESSION_TYPE_READ_WRITE ||
-					 conn->requested_session_type == SESSION_TYPE_PREFER_READ))
+					 conn->requested_session_type == SESSION_TYPE_PREFER_READ ||
+					 conn->requested_session_type == SESSION_TYPE_READ_ONLY))
 				{
 					if (conn->sversion < 120000)
 					{
@@ -3284,8 +3287,9 @@ keep_going:						/* We will come back to here until there is
 					else if ((conn->transaction_read_only &&
 							  conn->requested_session_type == SESSION_TYPE_READ_WRITE) ||
 							 (!conn->transaction_read_only &&
-							  conn->requested_session_type == SESSION_TYPE_PREFER_READ &&
-							  conn->read_write_host_index != -2))
+							  ((conn->requested_session_type == SESSION_TYPE_PREFER_READ &&
+								conn->read_write_host_index != -2) ||
+							   conn->requested_session_type == SESSION_TYPE_READ_ONLY)))
 					{
 						/* Not a requested type; fail this connection. */
 						const char *displayed_host;
@@ -3319,6 +3323,7 @@ keep_going:						/* We will come back to here until there is
 
 						/* Record read-write host index */
 						if (!conn->transaction_read_only &&
+							conn->requested_session_type == SESSION_TYPE_PREFER_READ &&
 							conn->read_write_host_index == -1)
 							conn->read_write_host_index = conn->whichhost;
 
@@ -3344,15 +3349,17 @@ keep_going:						/* We will come back to here until there is
 				 * Requested type is prefer-read, then record this host index
 				 * and try the other before considering it later
 				 */
-				if (conn->requested_session_type == SESSION_TYPE_PREFER_READ &&
-					conn->read_write_host_index != -2)
+				if ((conn->requested_session_type == SESSION_TYPE_PREFER_READ &&
+					 conn->read_write_host_index != -2) ||
+					conn->requested_session_type == SESSION_TYPE_READ_ONLY)
 				{
 					/* Close connection politely. */
 					conn->status = CONNECTION_OK;
 					sendTerminateConn(conn);
 
 					/* Record read-write host index */
-					if (conn->read_write_host_index == -1)
+					if (conn->requested_session_type == SESSION_TYPE_PREFER_READ &&
+						conn->read_write_host_index == -1)
 						conn->read_write_host_index = conn->whichhost;
 
 					/*
@@ -3443,8 +3450,9 @@ keep_going:						/* We will come back to here until there is
 					if ((readonly_server &&
 						 conn->requested_session_type == SESSION_TYPE_READ_WRITE) ||
 						(!readonly_server &&
-						 conn->requested_session_type == SESSION_TYPE_PREFER_READ &&
-						 conn->read_write_host_index != -2))
+						 ((conn->requested_session_type == SESSION_TYPE_PREFER_READ &&
+						   conn->read_write_host_index != -2) ||
+						  conn->requested_session_type == SESSION_TYPE_READ_ONLY)))
 					{
 						/* Not a requested type; fail this connection. */
 						PQclear(res);
@@ -3477,7 +3485,9 @@ keep_going:						/* We will come back to here until there is
 						sendTerminateConn(conn);
 
 						/* Record read-write host index */
-						if (!readonly_server && conn->read_write_host_index == -1)
+						if (!readonly_server &&
+							conn->requested_session_type == SESSION_TYPE_PREFER_READ &&
+							conn->read_write_host_index == -1)
 							conn->read_write_host_index = conn->whichhost;
 
 						/*
diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h
index 4b0fc80df2..5d0b885dae 100644
--- a/src/interfaces/libpq/libpq-fe.h
+++ b/src/interfaces/libpq/libpq-fe.h
@@ -74,7 +74,8 @@ typedef enum
 {
 	SESSION_TYPE_ANY = 0,		/* Any session (default) */
 	SESSION_TYPE_READ_WRITE,	/* Read-write session */
-	SESSION_TYPE_PREFER_READ	/* Prefer read only session */
+	SESSION_TYPE_PREFER_READ,	/* Prefer read only session */
+	SESSION_TYPE_READ_ONLY		/* Read only session */
 }			TargetSessionAttrsType;
 
 typedef enum
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index d6c482fd0b..8690f86956 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -365,7 +365,7 @@ struct pg_conn
 
 	/*
 	 * Type of connection to make.  Possible values: any, read-write,
-	 * prefer-read.
+	 * prefer-read and read-only.
 	 */
 	char	   *target_session_attrs;
 	TargetSessionAttrsType requested_session_type;
diff --git a/src/test/recovery/t/001_stream_rep.pl b/src/test/recovery/t/001_stream_rep.pl
index 0e398136a5..0d6f279586 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 => 29;
+use Test::More tests => 32;
 
 # Initialize master node
 my $node_master = get_new_node('master');
@@ -129,6 +129,18 @@ test_target_session_attrs($node_standby_1, $node_master, $node_standby_1,
 test_target_session_attrs($node_master, $node_master, $node_master,
 	"prefer-read", 0);
 
+# Connect to standby1 in "read-only" mode with master,standby1 list.
+test_target_session_attrs($node_master, $node_standby_1, $node_standby_1,
+	"read-only", 0);
+
+# Connect to standby1 in "read-only" mode with standby1,master list.
+test_target_session_attrs($node_standby_1, $node_master, $node_standby_1,
+	"read-only", 0);
+
+# Connection should fail in "read-only" mode with only master list.
+test_target_session_attrs($node_master, $node_master, $node_master,
+	"read-only", 1);
+
 note "switching to physical replication slot";
 
 # Switch to using a physical replication slot. We can do this without a new
-- 
2.20.1.windows.1

