On 2015.08.18 at 07:18:50 +0300, Victor Wagner wrote:

> Rationale
> =========
> 
> Since introduction of the WAL-based replication into the PostgreSQL, it is
> possible to create high-availability and load-balancing clusters.
> 
> However, there is no support for failover in the client libraries. So, only
> way to provide transparent for client application failover is IP address
> migration. This approach has some limitation, i.e. it requires that
> master and backup servers reside in the same subnet or may not be
> feasible for other reasons.
> 
> Commercial RDBMS, such as Oracle, employ more flexible approach. They
> allow to specify multiple servers in the connect string, so if primary
> server is not available, client library tries to connect to other ones.
> 
> This approach allows to use geographically distributed failover clusters
> and also is a cheap way to implement load-balancing (which is not
> possible with IP address migration).
> 

Attached patch which implements client library failover and
loadbalancing as was described in the proposal
<20150818041850.ga5...@wagner.pp.ru>.

This patch implements following fuctionality:

1. It is allowed to specify several hosts in the connect string, either
in URL-style (separated by comma) or in param=value form (several host
parameters).

2. Each host parameter can be accompanied with port specifier separated
by colon. Port, specified such way takes precedence of port parameter or
default port for this particular host only.

3. There is hostorder parameter with two possible valies 'sequential'
and 'random' (default sequential). 'parallel' hostorder described in the
proposal is not yet implemented in this version of patch.

4. In the URL-style connect string parameter loadBalanceHosts=true is
considered equal to 'hostorder=random' for compatibility with jdbc.

5. Added new parameter readonly=boolean. If this parameter is false (the
default) upon successful connection check is performed that backend is
not in the recovery state. If so, connection is not considered usable
and next host is tried.

6. Added new parameter falover_timeout. If no usable host is found and
this parameter is specified, hosts are retried cyclically until this
timeout expires. It gives some time for claster management software to
promote one of standbys to master if master fails. By default there is
no retries.

Some implementation notes:

1. Field  pghost in the PGconn structure now stores comma separated list
of hosts, which is parsed in the connectDBStart. So, expected results of 
some tests in src/interfaces/libpq/test are changed. 

2. Url with colon  but no port number after the host no more considered
valid.

3. With hostorder=random we have to seed libc random number gernerator.
Some care was taken to not to lose entropy if generator was
initialized by app before connection, and to ensure different random
values if several connections are made from same application in one
second (even in single thread). RNG is seeded by xor of current time,
random value from this rng before seeding (if it was seeded earlier, it
keeps entropy) and address of the connection structure. May be it worth
effort adding thread id or pid, but there is no portable way to doing
so, so it would need testing on all supported platform.



diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index 0ee018e..79a99ec 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -792,7 +792,7 @@ host=localhost port=5432 dbname=mydb connect_timeout=10
    <para>
    The general form for a connection <acronym>URI</acronym> is:
 <synopsis>
-postgresql://[user[:password]@][netloc][:port][/dbname][?param1=value1&amp;...]
+postgresql://[user[:password]@][netloc][:port][,netloc[:port]...][/dbname][?param1=value1&amp;...]
 </synopsis>
    </para>
 
@@ -809,6 +809,7 @@ postgresql://localhost/mydb
 postgresql://user@localhost
 postgresql://user:secret@localhost
 postgresql://other@localhost/otherdb?connect_timeout=10&amp;application_name=myapp
+postgresql://node1,node2:5433,node3:4432,node4/mydb?hostorder=random&amp;readonly=1
 </programlisting>
     Components of the hierarchical part of the <acronym>URI</acronym> can also
     be given as parameters.  For example:
@@ -831,7 +832,9 @@ postgresql:///mydb?host=localhost&amp;port=5433
    <para>
     For improved compatibility with JDBC connection <acronym>URI</acronym>s,
     instances of parameter <literal>ssl=true</literal> are translated into
-    <literal>sslmode=require</literal>.
+    <literal>sslmode=require</literal> and
+    <literal>loadBalanceHosts=true</literal>  into
+    <literal>hostorder=random</literal>.
    </para>
 
    <para>
@@ -841,6 +844,10 @@ postgresql:///mydb?host=localhost&amp;port=5433
 postgresql://[2001:db8::1234]/database
 </synopsis>
    </para>
+   <para>
+   There can be serveral host specifications, optionally accompanied
+   with port, separated by comma.
+   </para>
 
    <para>
     The host component is interpreted as described for the parameter <xref
@@ -881,6 +888,20 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
         when <productname>PostgreSQL</> was built). On machines without
         Unix-domain sockets, the default is to connect to <literal>localhost</>.
        </para>
+       <para>
+       There can be more than one <literal>host</literal> parameter in
+       the connect string. In this case these hosts would be considered
+       alternate entries into same database and if connect to first one
+       fails, library would try to connect second etc. This can be used
+       for high availability cluster or for load balancing. See
+       <xref linkend="libpq-connect-hostorder"> parameter.
+       </para>
+       <para>
+       Network host name can be accompanied with port number, separated by
+       colon. If so, this port number is used only when connected to
+       this host. If there is no port number, port specified in the
+       <xref linkend="libpq-connect-port"> parameter would be used.
+       </para>
       </listitem>
      </varlistentry>
 
@@ -942,8 +963,44 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
        </para>
        </listitem>
       </varlistentry>
-
+    
+      <varlistentry id="libpq-connect-hostorder" xreflabel="hostorder">
+      <term><literal>hostorder</literal></term>
+      <listitem>
+      <para>
+      Specifies how to choose host from list of alternate hosts,
+      specified in the <xref linkend="libpq-connect-host"> parameter.
+      </para>
+      <para>
+      If value of this argument is <literal>sequential</literal> (the
+      default) library connects to the hosts in order they specified,
+      and tries to connect second one only if connection to the first
+      fails.
+      </para>
+      <para>
+      If value is <literal>random</literal> host to connect is randomly
+      picked from the list. It allows to balance load between several
+      cluster nodes. However, currently PostgreSQL doesn't support
+      multimaster clusters. So, without use of third-party products,
+      only read-only connections can take advantage from the
+      load-balancing. See <xref linkend="libpq-connect-readonly">
+      </para>
+      </listitem>
+      </varlistentry>
+      <varlistentry id="libpq-connect-readonly" xreflabel="readonly">
+      <term><literal>readonly</literal></term>
+      <listitem>
+      <para>
+      If this parameter is 0 (the default), upon successful connection
+      library checks if host is in recovery state, and if it is so,
+      tries next host in the connect string. If this parameter is
+      non-zero, connection to warm standby nodes are considered
+      successful.
+      </para>
+      </listitem>
+      </varlistentry>
       <varlistentry id="libpq-connect-port" xreflabel="port">
+
        <term><literal>port</literal></term>
        <listitem>
        <para>
@@ -985,7 +1042,6 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
       </para>
       </listitem>
      </varlistentry>
-
      <varlistentry id="libpq-connect-connect-timeout" xreflabel="connect_timeout">
       <term><literal>connect_timeout</literal></term>
       <listitem>
@@ -996,7 +1052,27 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
       </para>
       </listitem>
      </varlistentry>
-
+     <varlistentry id="libpq-connect-falover-timeout" xreflabel="failover_timeout">
+     <term><literal>falover_timeout</literal></term>
+     <listitem>
+     <para>
+     Maximum time to cycilically retry all the hosts in commect string.
+     (as decimal integer number of seconds). If not specified, then
+     hosts are tried just once.
+     </para>
+     <para>
+     If we have replicating cluster, and master node fails, it might
+     take some time to promote one of standby nodes to the new master.
+     So clients which notice that connect to the master fails, can
+     already give up attempt to reestablish a connection when new master
+     became available. 
+     </para>
+     <para>
+     Setting this parameter to reasonable time makes library try to
+     reconnect all the host in cyclically until new master appears.
+     </para>
+     </listitem>
+     </varlistentry>
      <varlistentry id="libpq-connect-client-encoding" xreflabel="client_encoding">
       <term><literal>client_encoding</literal></term>
       <listitem>
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index f3030fb..eecafb5 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -1,5 +1,7 @@
 /*-------------------------------------------------------------------------
  *
+		UNIXSOCK_PATH(portstr, portnum, conn->pgunixsocket);
+
  * fe-connect.c
  *	  functions related to setting up a connection to the backend
  *
@@ -114,6 +116,7 @@ static int ldapServiceLookup(const char *purl, PQconninfoOption *options,
 #define DefaultSSLMode	"disable"
 #endif
 
+
 /* ----------
  * Definition of the conninfo parameters and their fallback resources.
  *
@@ -299,7 +302,16 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
 	{"replication", NULL, NULL, NULL,
 		"Replication", "D", 5,
 	offsetof(struct pg_conn, replication)},
-
+	/* Parameters added by failover patch */
+	{"hostorder", NULL, "sequential", NULL,
+		"Host order", "", 10,
+	offsetof(struct pg_conn, hostorder)},
+	{"readonly", NULL, "0", NULL,
+		"Read only", "", 1,
+	offsetof(struct pg_conn, read_only)},
+	{"failover_timeout", NULL, NULL, NULL,
+		"Failover Timoeut", "", 10,
+	offsetof(struct pg_conn, failover_timeout)},
 	/* Terminating entry --- MUST BE LAST */
 	{NULL, NULL, NULL, NULL,
 	NULL, NULL, 0}
@@ -380,6 +392,7 @@ static bool getPgPassFilename(char *pgpassfile);
 static void dot_pg_pass_warning(PGconn *conn);
 static void default_threadlock(int acquire);
 
+static int	try_next_address(PGconn *conn);
 
 /* global variable because fe-auth.c needs to access it */
 pgthreadlock_t pg_g_threadlock = default_threadlock;
@@ -1384,11 +1397,17 @@ setKeepalivesWin32(PGconn *conn)
 static int
 connectDBStart(PGconn *conn)
 {
+	struct nodeinfo
+	{
+		char	   *host;
+		char	   *port;
+	};
 	int			portnum;
 	char		portstr[MAXPGPATH];
 	struct addrinfo *addrs = NULL;
 	struct addrinfo hint;
-	const char *node;
+	struct nodeinfo *nodes,
+			   *node;
 	int			ret;
 
 	if (!conn)
@@ -1430,21 +1449,73 @@ connectDBStart(PGconn *conn)
 	if (conn->pghostaddr != NULL && conn->pghostaddr[0] != '\0')
 	{
 		/* Using pghostaddr avoids a hostname lookup */
-		node = conn->pghostaddr;
+
+		nodes = calloc(sizeof(struct nodeinfo), 2);
+		nodes->host = strdup(conn->pghostaddr);
 		hint.ai_family = AF_UNSPEC;
 		hint.ai_flags = AI_NUMERICHOST;
 	}
 	else if (conn->pghost != NULL && conn->pghost[0] != '\0')
 	{
 		/* Using pghost, so we have to look-up the hostname */
-		node = conn->pghost;
+		char	   *p = conn->pghost,
+				   *q,
+				   *r;
+		int			nodecount = 0,
+					nodesallocated = 4;
+
+		nodes = malloc(sizeof(struct nodeinfo) * 4);
+		while (*p)
+		{
+			q = p;
+			r = NULL;
+			while (*q != ',' && *q != 0)
+			{
+				if (*q == ':')
+					r = q;
+				if (*q == ']')
+					r = NULL;	/* if there is IPv6, colons before close
+								 * bracket are part of address */
+				q++;
+			}
+			if (r)
+			{
+				nodes[nodecount].port = malloc(q - r );
+				strncpy(nodes[nodecount].port, r + 1 , q - r);
+				/* FIXME need to check that port is numeric */
+				nodes[nodecount].port[q - r - 1] = 0;
+			}
+			else
+			{
+				r = q;
+				nodes[nodecount].port = NULL;
+			}
+			if ((*p) == '[' && *(r - 1) == ']')
+			{
+				/* IPv6 address found. Strip brackets */
+				p++;
+				r--;
+			}
+			nodes[nodecount].host = malloc(r - p + 1);
+			strncpy(nodes[nodecount].host, p, r - p);
+			nodes[nodecount].host[r - p] = 0;
+			/* skip a comma */
+			if (*q)
+				q++;
+			nodecount++;
+			if (nodecount == nodesallocated)
+				nodes = realloc(nodes, sizeof(struct nodeinfo) * (nodesallocated += 4));
+			p = q;
+		}
+		nodes[nodecount].host = NULL;
+		nodes[nodecount].port = NULL;
 		hint.ai_family = AF_UNSPEC;
 	}
 	else
 	{
 #ifdef HAVE_UNIX_SOCKETS
 		/* pghostaddr and pghost are NULL, so use Unix domain socket */
-		node = NULL;
+		nodes = calloc(sizeof(struct nodeinfo), 2);
 		hint.ai_family = AF_UNIX;
 		UNIXSOCK_PATH(portstr, portnum, conn->pgunixsocket);
 		if (strlen(portstr) >= UNIXSOCK_PATH_BUFLEN)
@@ -1454,33 +1525,73 @@ connectDBStart(PGconn *conn)
 							  portstr,
 							  (int) (UNIXSOCK_PATH_BUFLEN - 1));
 			conn->options_valid = false;
+			free(nodes->port);
+			free(nodes);
 			goto connect_errReturn;
 		}
+		nodes->port = strdup(portstr);
 #else
 		/* Without Unix sockets, default to localhost instead */
-		node = DefaultHost;
+		nodes = calloc(sizeof(struct nodeinfo), 2);
+		nodes->host = strdup(DefaultHost);
 		hint.ai_family = AF_UNSPEC;
 #endif   /* HAVE_UNIX_SOCKETS */
 	}
 
 	/* Use pg_getaddrinfo_all() to resolve the address */
-	ret = pg_getaddrinfo_all(node, portstr, &hint, &addrs);
-	if (ret || !addrs)
+	/* loop over all the host specs in the node variable */
+	for (node = nodes; node->host != NULL || node->port != NULL; node++)
 	{
-		if (node)
-			appendPQExpBuffer(&conn->errorMessage,
-							  libpq_gettext("could not translate host name \"%s\" to address: %s\n"),
-							  node, gai_strerror(ret));
+		struct addrinfo *this_node_addrs;
+
+		ret = pg_getaddrinfo_all(node->host, (node->port ? node->port : portstr),
+								 &hint, &this_node_addrs);
+		if (ret || !this_node_addrs)
+		{
+			if (node->host)
+				appendPQExpBuffer(&conn->errorMessage,
+								  libpq_gettext("could not translate host name \"%s\" to address: %s\n"),
+								  node->host, gai_strerror(ret));
+			else
+				appendPQExpBuffer(&conn->errorMessage,
+								  libpq_gettext("could not translate Unix-domain socket path \"%s\" to address: %s\n"),
+								  node->port, gai_strerror(ret));
+			if (this_node_addrs)
+				pg_freeaddrinfo_all(hint.ai_family, this_node_addrs);
+
+			/*
+			 * We shouldn't fail here unless there is no valid addrinfos left
+			 */
+			continue;
+		}
+		/* add this host addrs to addrs */
+		if (!addrs)
+		{
+			addrs = this_node_addrs;
+		}
 		else
-			appendPQExpBuffer(&conn->errorMessage,
-							  libpq_gettext("could not translate Unix-domain socket path \"%s\" to address: %s\n"),
-							  portstr, gai_strerror(ret));
-		if (addrs)
-			pg_freeaddrinfo_all(hint.ai_family, addrs);
+		{
+			struct addrinfo *p;
+
+			for (p = addrs; p->ai_next != NULL; p = p->ai_next);
+			p->ai_next = this_node_addrs;
+		}
+	}
+	/* Free nodes array */
+	for (node = nodes; node->host != NULL || node->port != NULL; node++)
+	{
+		if (node->host)
+			free(node->host);
+		if (node->port)
+			free(node->port);
+	}
+	free(nodes);
+	/* Check if we've found at least one usable address */
+	if (!addrs)
+	{
 		conn->options_valid = false;
 		goto connect_errReturn;
 	}
-
 #ifdef USE_SSL
 	/* setup values based on SSL mode */
 	if (conn->sslmode[0] == 'd')	/* "disable" */
@@ -1493,12 +1604,23 @@ connectDBStart(PGconn *conn)
 	 * Set up to try to connect, with protocol 3.0 as the first attempt.
 	 */
 	conn->addrlist = addrs;
-	conn->addr_cur = addrs;
+
+	/*
+	 * We cannot just assign first addrs record to addr_cur, because host
+	 * order may be random. So, use try_next_address
+	 */
+	conn->addr_cur = NULL;
+	try_next_address(conn);
 	conn->addrlist_family = hint.ai_family;
 	conn->pversion = PG_PROTOCOL(3, 0);
 	conn->send_appname = true;
 	conn->status = CONNECTION_NEEDED;
-
+	if (conn->failover_timeout) {
+		conn->failover_finish_time = time(NULL) +
+				atoi(conn->failover_timeout);
+	} else {
+		conn->failover_finish_time = (time_t)0; /* it is in past, so its ok */
+	}
 	/*
 	 * The code for processing CONNECTION_NEEDED state is in PQconnectPoll(),
 	 * so that it can easily be re-executed if needed again during the
@@ -1597,6 +1719,76 @@ connectDBComplete(PGconn *conn)
 	}
 }
 
+/* -------------
+ *	try_next_address
+ *	Attempts to set next address from the list of known ones.
+ *	Returns 1 if address is choosen and 0 if there are no more addresses
+ *	to try
+ *	Takes into account hostorder parameter
+ *	------------
+ */
+
+static int
+try_next_address(PGconn *conn)
+{
+	if (strcmp(conn->hostorder, "sequential") == 0)
+	{
+		if (conn->addr_cur == NULL)
+		{
+			conn->addr_cur = conn->addrlist;
+			return 1;
+		}
+		else
+		{
+			conn->addr_cur = conn->addr_cur->ai_next;
+			if (conn->addr_cur == NULL && time(NULL) < conn->failover_finish_time) {
+				/* If failover timeout is set, retry list of hosts from
+				 * the beginning */
+				conn->addr_cur = conn->addrlist;
+			}
+			return (conn->addr_cur != NULL);
+		}
+	}
+	else if (strcmp(conn->hostorder, "random") == 0)
+	{
+		struct addrinfo *choice = NULL,
+				   *current = conn->addrlist;
+		int			count = 0;
+		/* Initialize random number generator in case if nobody have
+		 * done it before. Use value from rand along with time in case
+		 * random number have been initialized by application.
+		 * Use address of conn structure to load-balance different
+		 * connections in the same app
+		 */
+		srand((unsigned int)((long int)conn  ^ (long int) time(NULL) ^
+					(long int)rand()));
+		while (current != NULL)
+		{
+			if (current == conn->addr_cur)
+			{
+				current = current->ai_next;
+			}
+			count++;
+			if ((rand()&0xffff) < 0x10000 / count)
+			{
+				choice = current;
+			}
+			current = current->ai_next;
+		}
+		if (choice == NULL)
+			return 0;
+		conn->addr_cur = choice;
+		return 1;
+	}
+	else if (strcmp(conn->hostorder, "parallel") == 0)
+	{
+		/* Not implemented yet */
+		return 0;
+	}
+	return 0;
+
+}
+
 /* ----------------
  *		PQconnectPoll
  *
@@ -1674,7 +1866,8 @@ PQconnectPoll(PGconn *conn)
 		case CONNECTION_SSL_STARTUP:
 		case CONNECTION_NEEDED:
 			break;
-
+		case CONNECTION_CHECK_RW:
+			break;
 		default:
 			appendPQExpBufferStr(&conn->errorMessage,
 								 libpq_gettext(
@@ -1712,9 +1905,8 @@ keep_going:						/* We will come back to here until there is
 						 * ignore socket() failure if we have more addresses
 						 * to try
 						 */
-						if (addr_cur->ai_next != NULL)
+						if (try_next_address(conn))
 						{
-							conn->addr_cur = addr_cur->ai_next;
 							continue;
 						}
 						appendPQExpBuffer(&conn->errorMessage,
@@ -1733,7 +1925,7 @@ keep_going:						/* We will come back to here until there is
 						if (!connectNoDelay(conn))
 						{
 							pqDropConnection(conn);
-							conn->addr_cur = addr_cur->ai_next;
+							try_next_address(conn);
 							continue;
 						}
 					}
@@ -1743,7 +1935,7 @@ keep_going:						/* We will come back to here until there is
 										  libpq_gettext("could not set socket to nonblocking mode: %s\n"),
 							SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf)));
 						pqDropConnection(conn);
-						conn->addr_cur = addr_cur->ai_next;
+						try_next_address(conn);
 						continue;
 					}
 
@@ -1754,7 +1946,7 @@ keep_going:						/* We will come back to here until there is
 										  libpq_gettext("could not set socket to close-on-exec mode: %s\n"),
 							SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf)));
 						pqDropConnection(conn);
-						conn->addr_cur = addr_cur->ai_next;
+						try_next_address(conn);
 						continue;
 					}
 #endif   /* F_SETFD */
@@ -1801,7 +1993,7 @@ keep_going:						/* We will come back to here until there is
 						if (err)
 						{
 							pqDropConnection(conn);
-							conn->addr_cur = addr_cur->ai_next;
+							try_next_address(conn);
 							continue;
 						}
 					}
@@ -1892,7 +2084,7 @@ keep_going:						/* We will come back to here until there is
 					/*
 					 * Try the next address, if any.
 					 */
-					conn->addr_cur = addr_cur->ai_next;
+					try_next_address(conn);
 				}				/* loop over addresses */
 
 				/*
@@ -1938,9 +2130,8 @@ keep_going:						/* We will come back to here until there is
 					 * If more addresses remain, keep trying, just as in the
 					 * case where connect() returned failure immediately.
 					 */
-					if (conn->addr_cur->ai_next != NULL)
+					if (try_next_address(conn))
 					{
-						conn->addr_cur = conn->addr_cur->ai_next;
 						conn->status = CONNECTION_NEEDED;
 						goto keep_going;
 					}
@@ -2608,8 +2799,9 @@ keep_going:						/* We will come back to here until there is
 				}
 
 				/* Otherwise, we are open for business! */
-				conn->status = CONNECTION_OK;
-				return PGRES_POLLING_OK;
+				conn->status = CONNECTION_CHECK_RO;
+				goto keep_going;
+
 			}
 
 		case CONNECTION_SETENV:
@@ -2639,9 +2831,81 @@ keep_going:						/* We will come back to here until there is
 					goto error_return;
 			}
 
-			/* We are open for business! */
+			/*
+			 * check if connectionV is readonly if we need readwrite one
+			 */
+			conn->status = CONNECTION_CHECK_RO;
+			goto keep_going;
+
+		case CONNECTION_CHECK_RO:
+
+			/*
+			 * consult connection options and check if RO connection is OK
+			 */
+			if (conn->read_only && conn->read_only[0] > '0')
+			{
+				conn->status = CONNECTION_OK;
+				return PGRES_POLLING_OK;
+			}
+			/* Otherwise request result pg_is_in_recovery() */
+			/* pretend that status is OK for time of sending query */
 			conn->status = CONNECTION_OK;
-			return PGRES_POLLING_OK;
+			PQsendQuery(conn, "SELECT pg_is_in_recovery()");
+			conn->status = CONNECTION_CHECK_RW;
+			return PGRES_POLLING_READING;
+		case CONNECTION_CHECK_RW:
+			{
+				char	   *value;
+				PGresult   *res;
+
+				conn->status = CONNECTION_OK;
+				if (!PQconsumeInput(conn))
+				{
+					conn->status = CONNECTION_BAD;
+					return PGRES_POLLING_FAILED;
+				}
+				if (PQisBusy(conn))
+				{
+					/* Result is not ready yet */
+					conn->status = CONNECTION_CHECK_RW;
+					return PGRES_POLLING_READING;
+				}
+				res = PQgetResult(conn);
+				if (PQresultStatus(res) != PGRES_TUPLES_OK)
+				{
+					/*
+					 * Something wrong happened with this host. skip to next
+					 * one
+					 */
+					conn->status = CONNECTION_NEEDED;
+				}
+				value = PQgetvalue(res, 0, 0);
+				if (strcmp(value, "true") == 0)
+				{
+					conn->status = CONNECTION_NEEDED;
+				}
+				PQclear(res);
+				if (conn->status != CONNECTION_OK)
+				{
+					ConnStatusType save_status = conn->status;
+
+					conn->status = CONNECTION_OK;
+					closePGconn(conn);
+					conn->sock = PGINVALID_SOCKET;
+					if (try_next_address(conn))
+					{
+						conn->status = save_status;
+						goto keep_going;
+					}
+					else
+					{
+						conn->status = CONNECTION_BAD;
+						return PGRES_POLLING_FAILED;
+					}
+
+				}
+				return PGRES_POLLING_OK;
+			}
 
 		default:
 			appendPQExpBuffer(&conn->errorMessage,
@@ -4798,87 +5062,118 @@ conninfo_uri_parse_options(PQconninfoOption *options, const char *uri,
 		 */
 		p = start;
 	}
-
-	/*
-	 * "p" has been incremented past optional URI credential information at
-	 * this point and now points at the "netloc" part of the URI.
-	 *
-	 * Look for IPv6 address.
-	 */
-	if (*p == '[')
-	{
-		host = ++p;
-		while (*p && *p != ']')
-			++p;
-		if (!*p)
+	host = p;
+	if (*p==':') {
+		int			portnum;
+		char *portstr;
+		*(p++)='\0';
+		portstr = p;
+		portnum = 0;
+		while (*p >= '0' && *p <= '9')
 		{
-			printfPQExpBuffer(errorMessage,
-							  libpq_gettext("end of string reached when looking for matching \"]\" in IPv6 host address in URI: \"%s\"\n"),
-							  uri);
-			goto cleanup;
+			portnum = portnum * 10 + (*(p++) - '0');
 		}
-		if (p == host)
+		if (portnum > 65535 || portnum <1)
 		{
 			printfPQExpBuffer(errorMessage,
-							  libpq_gettext("IPv6 host address may not be empty in URI: \"%s\"\n"),
-							  uri);
+							libpq_gettext("invalid port number: \"%d\"\n"),
+								portnum);
 			goto cleanup;
 		}
+		prevchar = *p;
+		*p='\0';
+		if (*portstr &&
+				!conninfo_storeval(options, "port", portstr,
+								   errorMessage, false, true));
 
-		/* Cut off the bracket and advance */
-		*(p++) = '\0';
-
-		/*
-		 * The address may be followed by a port specifier or a slash or a
-		 * query.
-		 */
-		if (*p && *p != ':' && *p != '/' && *p != '?')
+	} else {
+		do
 		{
-			printfPQExpBuffer(errorMessage,
-							  libpq_gettext("unexpected character \"%c\" at position %d in URI (expected \":\" or \"/\"): \"%s\"\n"),
-							  *p, (int) (p - buf + 1), uri);
-			goto cleanup;
-		}
-	}
-	else
-	{
-		/* not an IPv6 address: DNS-named or IPv4 netloc */
-		host = p;
+			if (*p == ',')
+				p++;
 
-		/*
-		 * Look for port specifier (colon) or end of host specifier (slash),
-		 * or query (question mark).
-		 */
-		while (*p && *p != ':' && *p != '/' && *p != '?')
-			++p;
-	}
-
-	/* Save the hostname terminator before we null it */
-	prevchar = *p;
-	*p = '\0';
+			/*
+			* "p" has been incremented past optional URI credential information
+			* at this point and now points at the "netloc" part of the URI.
+			*
+			* Look for IPv6 address.
+			*/
+			if (*p == '[')
+			{
+				char	   *ipv6start = ++p;
 
-	if (*host &&
-		!conninfo_storeval(options, "host", host,
-						   errorMessage, false, true))
-		goto cleanup;
+				while (*p && *p != ']')
+					++p;
+				if (!*p)
+				{
+					printfPQExpBuffer(errorMessage,
+									libpq_gettext("end of string reached when looking for matching \"]\" in IPv6 host address in URI: \"%s\"\n"),
+									uri);
+					goto cleanup;
+				}
+				if (p == ipv6start)
+				{
+					printfPQExpBuffer(errorMessage,
+									libpq_gettext("IPv6 host address may not be empty in URI: \"%s\"\n"),
+									uri);
+					goto cleanup;
+				}
 
+				p++;
+				/*
+				* The address may be followed by a port specifier, a comma or a
+				* slash or a query.
+				*/
+				if (*p && *p != ',' && *p != ':' && *p != '/' && *p != '?')
+				{
+					printfPQExpBuffer(errorMessage,
+									libpq_gettext("unexpected character \"%c\" at position %d in URI (expected \":\" or \"/\"): \"%s\"\n"),
+									*p, (int) (p - buf + 1), uri);
+					goto cleanup;
+				}
 
-	if (prevchar == ':')
-	{
-		const char *port = ++p; /* advance past host terminator */
+			}
+			else
+			{
+				/* not an IPv6 address: DNS-named or IPv4 netloc */
 
-		while (*p && *p != '/' && *p != '?')
-			++p;
+				/*
+				* Look for port specifier (colon) or end of host specifier
+				* (slash), or query (question mark).
+				*/
+				while (*p && *p != ',' && *p != ':' && *p != '/' && *p != '?')
+					++p;
+			}
+			/* Skip port specifier */
+			if (*p == ':')
+			{
+				int			portnum;
 
+				p++;
+				portnum = 0;
+				while (*p >= '0' && *p <= '9')
+				{
+					portnum = portnum * 10 + (*(p++) - '0');
+				}
+				if (portnum > 65535 || portnum <1)
+				{
+					printfPQExpBuffer(errorMessage,
+								libpq_gettext("invalid port number: \"%d\"\n"),
+									portnum);
+					goto cleanup;
+				}
+			}
+		} while (*p == ',');
+		/* Save the hostname terminator before we null it */
 		prevchar = *p;
 		*p = '\0';
-
-		if (*port &&
-			!conninfo_storeval(options, "port", port,
-							   errorMessage, false, true))
+		
+		if (*host &&
+			!conninfo_storeval(options, "host", host,
+							errorMessage, false, true))
 			goto cleanup;
-	}
 
+	}
 	if (prevchar && prevchar != '?')
 	{
 		const char *dbname = ++p;		/* advance past host terminator */
@@ -5008,6 +5303,16 @@ conninfo_uri_parse_params(char *params,
 			keyword = "sslmode";
 			value = "require";
 		}
+		if ((strcmp(keyword, "loadBalanceHosts") == 0 ||
+			 strcmp(keyword, "load_balance_hosts") == 0) &&
+			strcmp(value, "true") == 0)
+		{
+			free(keyword);
+			free(value);
+			malloced = false;
+			keyword = "hostorder";
+			value = "random";
+		}
 
 		/*
 		 * Store the value if the corresponding option exists; ignore
@@ -5222,7 +5527,22 @@ conninfo_storeval(PQconninfoOption *connOptions,
 	}
 
 	if (option->val)
+	{
+		if (strcmp(option->keyword, "host") == 0)
+		{
+			/* Accumulate multiple hosts in the single string */
+			int			val_len = strlen(option->val),
+						new_len = strlen(value);
+
+			free(value_copy);
+			value_copy = malloc(val_len + 1 + new_len + 1);
+			strncpy(value_copy, option->val, val_len + 1);
+			value_copy[val_len] = ',';
+			strncpy(value_copy + val_len + 1, value, new_len + 1);
+		}
 		free(option->val);
+
+	}
 	option->val = value_copy;
 
 	return option;
diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h
index 828c533..20899bc 100644
--- a/src/interfaces/libpq/libpq-fe.h
+++ b/src/interfaces/libpq/libpq-fe.h
@@ -62,7 +62,11 @@ typedef enum
 								 * backend startup. */
 	CONNECTION_SETENV,			/* Negotiating environment. */
 	CONNECTION_SSL_STARTUP,		/* Negotiating SSL. */
-	CONNECTION_NEEDED			/* Internal state: connect() needed */
+	CONNECTION_NEEDED,			/* Internal state: connect() needed */
+	CONNECTION_CHECK_RO,		/* Internal state: need to check is RO
+								 * connection acceptable */
+	CONNECTION_CHECK_RW,		/* Internal state: waiting that server replies
+								 * if it is in recovery */
 } ConnStatusType;
 
 typedef enum
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 04d056e..164c858 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -334,7 +334,11 @@ struct pg_conn
 #if defined(ENABLE_GSS) || defined(ENABLE_SSPI)
 	char	   *krbsrvname;		/* Kerberos service name */
 #endif
-
+	char	   *hostorder;		/* How to handle multiple hosts */
+	char	   *read_only;		/* If true, we could work with readonly
+								 * standby server */
+	char	   *failover_timeout;		/* If no usable server found, how long
+										 * to wait before retry */
 	/* Optional file to write trace info to */
 	FILE	   *Pfdebug;
 
@@ -382,10 +386,12 @@ struct pg_conn
 	struct addrinfo *addrlist;	/* list of possible backend addresses */
 	struct addrinfo *addr_cur;	/* the one currently being tried */
 	int			addrlist_family;	/* needed to know how to free addrlist */
+	time_t failover_finish_time;	/* how long to retry host list
+									 *waiting for new master to appear */
 	PGSetenvStatusType setenv_state;	/* for 2.0 protocol only */
 	const PQEnvironmentOption *next_eo;
 	bool		send_appname;	/* okay to send application_name? */
-
+	
 	/* Miscellaneous stuff */
 	int			be_pid;			/* PID of backend --- needed for cancels */
 	int			be_key;			/* key of backend --- needed for cancels */
diff --git a/src/interfaces/libpq/test/expected.out b/src/interfaces/libpq/test/expected.out
index d375e82..4832bdd 100644
--- a/src/interfaces/libpq/test/expected.out
+++ b/src/interfaces/libpq/test/expected.out
@@ -1,20 +1,20 @@
 trying postgresql://uri-user:secret@host:12345/db
-user='uri-user' password='secret' dbname='db' host='host' port='12345' (inet)
+user='uri-user' password='secret' dbname='db' host='host:12345' (inet)
 
 trying postgresql://uri-user@host:12345/db
-user='uri-user' dbname='db' host='host' port='12345' (inet)
+user='uri-user' dbname='db' host='host:12345' (inet)
 
 trying postgresql://uri-user@host/db
 user='uri-user' dbname='db' host='host' (inet)
 
 trying postgresql://host:12345/db
-dbname='db' host='host' port='12345' (inet)
+dbname='db' host='host:12345' (inet)
 
 trying postgresql://host/db
 dbname='db' host='host' (inet)
 
 trying postgresql://uri-user@host:12345/
-user='uri-user' host='host' port='12345' (inet)
+user='uri-user' host='host:12345' (inet)
 
 trying postgresql://uri-user@host/
 user='uri-user' host='host' (inet)
@@ -23,10 +23,10 @@ trying postgresql://uri-user@
 user='uri-user' (local)
 
 trying postgresql://host:12345/
-host='host' port='12345' (inet)
+host='host:12345' (inet)
 
 trying postgresql://host:12345
-host='host' port='12345' (inet)
+host='host:12345' (inet)
 
 trying postgresql://host/db
 dbname='db' host='host' (inet)
@@ -62,7 +62,7 @@ trying postgresql://host/db?u%7aer=someotheruser&port=12345
 uri-regress: invalid URI query parameter: "uzer"
 
 trying postgresql://host:12345?user=uri-user
-user='uri-user' host='host' port='12345' (inet)
+user='uri-user' host='host:12345' (inet)
 
 trying postgresql://host?user=uri-user
 user='uri-user' host='host' (inet)
@@ -71,19 +71,19 @@ trying postgresql://host?
 host='host' (inet)
 
 trying postgresql://[::1]:12345/db
-dbname='db' host='::1' port='12345' (inet)
+dbname='db' host='[::1]:12345' (inet)
 
 trying postgresql://[::1]/db
-dbname='db' host='::1' (inet)
+dbname='db' host='[::1]' (inet)
 
 trying postgresql://[2001:db8::1234]/
-host='2001:db8::1234' (inet)
+host='[2001:db8::1234]' (inet)
 
 trying postgresql://[200z:db8::1234]/
-host='200z:db8::1234' (inet)
+host='[200z:db8::1234]' (inet)
 
 trying postgresql://[::1]
-host='::1' (inet)
+host='[::1]' (inet)
 
 trying postgres://
 (local)
@@ -143,7 +143,7 @@ trying postgres://@host
 host='host' (inet)
 
 trying postgres://host:/
-host='host' (inet)
+uri-regress: invalid port number: "0"
 
 trying postgres://:12345/
 port='12345' (local)
-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Reply via email to