Hi,

I would like allow specifying multiple host names for libpq to try to 
connecting to. This is currently only supported if the host name resolves to 
multiple addresses. Having the support for it without complex dns setup would 
be much easier.


Example:

psql -h dbslave,dbmaster -p 5432 dbname

psql 'postgresql://dbslave,dbmaster:5432/dbname'


Here the idea is that without any added complexity of pgbouncer or similar tool 
I can get any libpq client to try connecting to multiple nodes until one 
answers. I have added the similar functionality to the jdbc driver few years 
ago.


Because libpq almost supported the feature already the patch is very simple. I 
just split the given host name and do a dns lookup on each separately, and link 
the results.


If you configure a port that does not exist you can see the libpq trying to 
connect to multiple hosts.


psql -h 127.0.0.2,127.0.0.3, -p 5555

psql: could not connect to server: Connection refused
    Is the server running on host "127.0.0.2,127.0.0.3" (127.0.0.2) and 
accepting
    TCP/IP connections on port 5555?
could not connect to server: Connection refused
    Is the server running on host "127.0.0.2,127.0.0.3" (127.0.0.3) and 
accepting
    TCP/IP connections on port 5555?


Further improvement would be to add a connection parameter to limit connection 
only to master (writable) or to slave (read only).



-Mikko
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 62a3b21..e79f96c 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -232,6 +232,12 @@ EOF
       with a slash, it is used as the directory for the Unix-domain
       socket.
       </para>
+      <para>
+      It is possible to provide multiple host names to connect to. The first host name that accepts
+      the connection will be used. To explicitly specify multiple host names they must be separated
+      by comma.
+      An alternative is to use dns record for the host name that resolves to multiple addresses.
+      </para>
       </listitem>
     </varlistentry>
 
diff --git a/src/backend/libpq/ip.c b/src/backend/libpq/ip.c
index db939b5..90d3c1e 100644
--- a/src/backend/libpq/ip.c
+++ b/src/backend/libpq/ip.c
@@ -56,6 +56,9 @@ static int getnameinfo_unix(const struct sockaddr_un * sa, int salen,
 				 int flags);
 #endif
 
+/* Marker for multiple dns resolver results linked together */
+#define AI_PG_LINKED	(1<<30)
+
 
 /*
  *	pg_getaddrinfo_all - get address info for Unix, IPv4 and IPv6 sockets
@@ -65,6 +68,9 @@ pg_getaddrinfo_all(const char *hostname, const char *servname,
 				   const struct addrinfo * hintp, struct addrinfo ** result)
 {
 	int			rc;
+	char			*hostnames;
+	char			*host_start;
+	struct addrinfo	*addr;
 
 	/* not all versions of getaddrinfo() zero *result on failure */
 	*result = NULL;
@@ -73,10 +79,46 @@ pg_getaddrinfo_all(const char *hostname, const char *servname,
 	if (hintp->ai_family == AF_UNIX)
 		return getaddrinfo_unix(servname, hintp, result);
 #endif
-
 	/* NULL has special meaning to getaddrinfo(). */
-	rc = getaddrinfo((!hostname || hostname[0] == '\0') ? NULL : hostname,
-					 servname, hintp, result);
+	if (!hostname || hostname[0] == '\0')
+		return getaddrinfo(NULL, servname, hintp, result);
+
+	hostnames = strdup(hostname);
+	if (!hostnames)
+		return EAI_MEMORY;
+
+	host_start = hostnames;
+
+	/* dns lookups in reverse order to make result links in correct order */
+	do
+	{
+		host_start = strrchr(hostnames, ',');
+		if (host_start)
+		{
+			*host_start = '\0';
+			host_start++;
+		}
+		else
+			host_start = hostnames;
+
+		addr = NULL;
+		rc = getaddrinfo(host_start, servname, hintp, &addr);
+		if (rc || !addr)
+		{
+			if (addr)
+				freeaddrinfo(addr);
+			break;
+		}
+		if (*result)
+		{
+			(*result)->ai_flags |= AI_PG_LINKED;
+			addr->ai_next = *result;
+		}
+		*result = addr;
+
+	} while (host_start > hostnames);
+
+	free(hostnames);
 
 	return rc;
 }
@@ -94,6 +136,9 @@ pg_getaddrinfo_all(const char *hostname, const char *servname,
 void
 pg_freeaddrinfo_all(int hint_ai_family, struct addrinfo * ai)
 {
+	struct addrinfo * last_addrinfo;
+	struct addrinfo * ai_ptr;
+
 #ifdef HAVE_UNIX_SOCKETS
 	if (hint_ai_family == AF_UNIX)
 	{
@@ -109,10 +154,24 @@ pg_freeaddrinfo_all(int hint_ai_family, struct addrinfo * ai)
 	}
 	else
 #endif   /* HAVE_UNIX_SOCKETS */
+	/* struct was built by getaddrinfo() */
+	if (ai != NULL)
 	{
-		/* struct was built by getaddrinfo() */
-		if (ai != NULL)
-			freeaddrinfo(ai);
+		while (true)
+		{
+			/* unlink extra addrinfo structures before freeing */
+			last_addrinfo = NULL;
+			for (ai_ptr = ai; ai_ptr->ai_next; ai_ptr = ai_ptr->ai_next)
+			{
+				if (ai_ptr->ai_next->ai_flags & AI_PG_LINKED)
+					last_addrinfo = ai_ptr;
+			}
+			if (!last_addrinfo)
+				break;
+			freeaddrinfo(last_addrinfo->ai_next);
+			last_addrinfo->ai_next = NULL;
+		}
+		freeaddrinfo(ai);
 	}
 }
 
-- 
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