]> granicus.if.org Git - postgresql/commitdiff
libpq: Allow connection strings and URIs to specify multiple hosts.
authorRobert Haas <rhaas@postgresql.org>
Thu, 3 Nov 2016 13:25:20 +0000 (09:25 -0400)
committerRobert Haas <rhaas@postgresql.org>
Thu, 3 Nov 2016 13:25:20 +0000 (09:25 -0400)
It's also possible to specify a separate port for each host.

Previously, we'd loop over every address returned by looking up the
host name; now, we'll try every address for every host name.

Patch by me.  Victor Wagner wrote an earlier patch for this feature,
which I read, but I didn't use any of his code.  Review by Mithun Cy.

doc/src/sgml/libpq.sgml
src/interfaces/libpq/fe-auth.c
src/interfaces/libpq/fe-connect.c
src/interfaces/libpq/libpq-int.h

index 4e34f00e44de465f4e9ac97ae3d8bcf82f1c0d14..d04dba7493b2d08c9faa7323e0e90b6a1783de39 100644 (file)
@@ -756,8 +756,10 @@ PGPing PQping(const char *conninfo);
     Several <application>libpq</> functions parse a user-specified string to obtain
     connection parameters.  There are two accepted formats for these strings:
     plain <literal>keyword = value</literal> strings
-    and <ulink url="http://www.ietf.org/rfc/rfc3986.txt">RFC
-    3986</ulink> URIs.
+    and URIs.  URIs generally follow
+    <ulink url="http://www.ietf.org/rfc/rfc3986.txt">RFC
+    3986</ulink>, except that multi-host connection strings are allowed
+    as further described below.
    </para>
 
    <sect3>
@@ -792,7 +794,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][,...][/dbname][?param1=value1&amp;...]
 </synopsis>
    </para>
 
@@ -809,6 +811,7 @@ postgresql://localhost/mydb
 postgresql://user@localhost
 postgresql://user:secret@localhost
 postgresql://other@localhost/otherdb?connect_timeout=10&amp;application_name=myapp
+postgresql://host1:123,host2:456/somedb
 </programlisting>
     Components of the hierarchical part of the <acronym>URI</acronym> can also
     be given as parameters.  For example:
@@ -856,6 +859,15 @@ postgresql:///dbname?host=/var/lib/postgresql
 postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
 </programlisting>
    </para>
+
+   <para>
+    It is possible to specify multiple host components, each with an optional
+    port component, in a single URI.  A URI of the form
+    <literal>postgresql://host1:port1,host2:port2,host3:port3/</literal>
+    is equivalent to a connection string of the form
+    <literal>host=host1,host2,host3 port=port1,port2,port3</literal>.  Each
+    host will be tried in turn until a connection is successfully established.
+   </para>
    </sect3>
   </sect2>
 
@@ -870,12 +882,13 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
       <term><literal>host</literal></term>
       <listitem>
        <para>
-        Name of host to connect to.<indexterm><primary>host name</></>
-        If this begins with a slash, it specifies Unix-domain
+        Comma-separated list of host names.<indexterm><primary>host name</></>
+        If a host name begins with a slash, it specifies Unix-domain
         communication rather than TCP/IP communication; the value is the
-        name of the directory in which the socket file is stored.  The
-        default behavior when <literal>host</literal> is not specified
-        is to connect to a Unix-domain
+        name of the directory in which the socket file is stored.  If
+        multiple host names are specified, each will be tried in turn in
+        the order given.  The default behavior when <literal>host</literal> is
+        not specified is to connect to a Unix-domain
         socket<indexterm><primary>Unix domain socket</></> in
         <filename>/tmp</filename> (or whatever socket directory was specified
         when <productname>PostgreSQL</> was built). On machines without
@@ -950,6 +963,9 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
         Port number to connect to at the server host, or socket file
         name extension for Unix-domain
         connections.<indexterm><primary>port</></>
+        If the <literal>host</> parameter included multiple, comma-separated
+        hosts, this parameter may specify a list of ports of equal length,
+        or it may specify a single port number to be used for all hosts.
        </para>
       </listitem>
      </varlistentry>
@@ -1394,7 +1410,11 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
 
   <para>
    The following functions return parameter values established at connection.
-   These values are fixed for the life of the <structname>PGconn</> object.
+   These values are fixed for the life of the connection.  If a multi-host
+   connection string is used, the values of <function>PQhost</>,
+   <function>PQport</>, and <function>PQpass</> can change if a new connection
+   is established using the same <structname>PGconn</> object.  Other values
+   are fixed for the lifetime of the <structname>PGconn</> object.
 
    <variablelist>
     <varlistentry id="libpq-pqdb">
index 404bc93306d00ff2b8c219ce929b7e0051852278..19171fb676af103e5d44d40b743826d07c4be89c 100644 (file)
@@ -683,20 +683,26 @@ pg_fe_sendauth(AuthRequest areq, PGconn *conn)
 
                case AUTH_REQ_MD5:
                case AUTH_REQ_PASSWORD:
-                       conn->password_needed = true;
-                       if (conn->pgpass == NULL || conn->pgpass[0] == '\0')
                        {
-                               printfPQExpBuffer(&conn->errorMessage,
-                                                                 PQnoPasswordSupplied);
-                               return STATUS_ERROR;
-                       }
-                       if (pg_password_sendauth(conn, conn->pgpass, areq) != STATUS_OK)
-                       {
-                               printfPQExpBuffer(&conn->errorMessage,
+                               char   *password = conn->connhost[conn->whichhost].password;
+
+                               if (password == NULL)
+                                       password = conn->pgpass;
+                               conn->password_needed = true;
+                               if (password == NULL || password[0] == '\0')
+                               {
+                                       printfPQExpBuffer(&conn->errorMessage,
+                                                                         PQnoPasswordSupplied);
+                                       return STATUS_ERROR;
+                               }
+                               if (pg_password_sendauth(conn, password, areq) != STATUS_OK)
+                               {
+                                       printfPQExpBuffer(&conn->errorMessage,
                                         "fe_sendauth: error sending password authentication\n");
-                               return STATUS_ERROR;
+                                       return STATUS_ERROR;
+                               }
+                               break;
                        }
-                       break;
 
                case AUTH_REQ_SCM_CREDS:
                        if (pg_local_sendauth(conn) != STATUS_OK)
index f3a9e5a83fec8239b2dcbecd97a3799e0e6fc781..b4f9ad72e598edadc285a76fc7ba7987162ec7fa 100644 (file)
@@ -770,6 +770,148 @@ connectOptions1(PGconn *conn, const char *conninfo)
 static bool
 connectOptions2(PGconn *conn)
 {
+       /*
+        * Allocate memory for details about each host to which we might possibly
+        * try to connect.  If pghostaddr is set, we're only going to try to
+        * connect to that one particular address.  If it's not, we'll use pghost,
+        * which may contain multiple, comma-separated names.
+        */
+       conn->nconnhost = 1;
+       conn->whichhost = 0;
+       if ((conn->pghostaddr == NULL || conn->pghostaddr[0] == '\0')
+               && conn->pghost != NULL)
+       {
+               char   *s;
+
+               for (s = conn->pghost; *s != '\0'; ++s)
+                       if (*s == ',')
+                               conn->nconnhost++;
+       }
+       conn->connhost = (pg_conn_host *)
+               calloc(conn->nconnhost, sizeof(pg_conn_host));
+       if (conn->connhost == NULL)
+               goto oom_error;
+
+       /*
+        * We now have one pg_conn_host structure per possible host.  Fill in
+        * the host details for each one.
+        */
+       if (conn->pghostaddr != NULL && conn->pghostaddr[0] != '\0')
+       {
+               conn->connhost[0].host = strdup(conn->pghostaddr);
+               if (conn->connhost[0].host == NULL)
+                       goto oom_error;
+               conn->connhost[0].type = CHT_HOST_ADDRESS;
+       }
+       else if (conn->pghost != NULL && conn->pghost[0] != '\0')
+       {
+               int             i = 0;
+               char   *s = conn->pghost;
+
+               while (1)
+               {
+                       char   *e = s;
+
+                       /*
+                        * Search for the end of the current hostname; a comma or
+                        * end-of-string acts as a terminator.
+                        */
+                       while (*e != '\0' && *e != ',')
+                               ++e;
+
+                       /* Copy the hostname whose bounds we just identified. */
+                       conn->connhost[i].host =
+                               (char *) malloc(sizeof(char) * (e - s + 1));
+                       if (conn->connhost[i].host == NULL)
+                               goto oom_error;
+                       memcpy(conn->connhost[i].host, s, e - s);
+                       conn->connhost[i].host[e - s] = '\0';
+
+                       /* Identify the type of host. */
+                       conn->connhost[i].type = CHT_HOST_NAME;
+#ifdef HAVE_UNIX_SOCKETS
+                       if (is_absolute_path(conn->connhost[i].host))
+                               conn->connhost[i].type = CHT_UNIX_SOCKET;
+#endif
+
+                       /* Prepare to find the next host (if any). */
+                       if (*e == '\0')
+                               break;
+                       s = e + 1;
+                       i++;
+               }
+       }
+       else
+       {
+#ifdef HAVE_UNIX_SOCKETS
+               conn->connhost[0].host = strdup(DEFAULT_PGSOCKET_DIR);
+               conn->connhost[0].type = CHT_UNIX_SOCKET;
+#else
+               conn->connhost[0].host = strdup(DefaultHost);
+               conn->connhost[0].type = CHT_HOST_NAME;
+#endif
+               if (conn->connhost[0].host == NULL)
+                       goto oom_error;
+       }
+
+       /*
+        * Next, work out the port number corresponding to each host name.
+        */
+       if (conn->pgport != NULL && conn->pgport[0] != '\0')
+       {
+               int             i = 0;
+               char   *s = conn->pgport;
+               int             nports = 1;
+
+               for (i = 0; i < conn->nconnhost; ++i)
+               {
+                       char   *e = s;
+
+                       /* Search for the end of the current port number. */
+                       while (*e != '\0' && *e != ',')
+                               ++e;
+
+                       /*
+                        * If we found a port number of non-zero length, copy it.
+                        * Otherwise, insert the default port number.
+                        */
+                       if (e > s)
+                       {
+                               conn->connhost[i].port =
+                                       (char *) malloc(sizeof(char) * (e - s + 1));
+                               if (conn->connhost[i].port == NULL)
+                                       goto oom_error;
+                               memcpy(conn->connhost[i].port, s, e - s);
+                               conn->connhost[i].port[e - s] = '\0';
+                       }
+
+                       /*
+                        * Move on to the next port number, unless there are no more.
+                        * (If only one part number is specified, we reuse it for every
+                        * host.)
+                        */
+                       if (*e != '\0')
+                       {
+                               s = e + 1;
+                               ++nports;
+                       }
+               }
+
+               /*
+                * If multiple ports were specified, there must be exactly as many
+                * ports as there were hosts.  Otherwise, we do not know how to match
+                * them up.
+                */
+               if (nports != 1 && nports != conn->nconnhost)
+               {
+                       conn->status = CONNECTION_BAD;
+                       printfPQExpBuffer(&conn->errorMessage,
+                               libpq_gettext("could not match %d port numbers to %d hosts\n"),
+                                                         nports, conn->nconnhost);
+                       return false;
+               }
+       }
+
        /*
         * If user name was not given, fetch it.  (Most likely, the fetch will
         * fail, since the only way we get here is if pg_fe_getauthname() failed
@@ -800,33 +942,27 @@ connectOptions2(PGconn *conn)
        }
 
        /*
-        * Supply default password if none given
+        * Supply default password if none given.  Note that the password might
+        * be different for each host/port pair.
         */
        if (conn->pgpass == NULL || conn->pgpass[0] == '\0')
        {
+               int             i;
+
                if (conn->pgpass)
                        free(conn->pgpass);
-               conn->pgpass = PasswordFromFile(conn->pghost, conn->pgport,
-                                                                               conn->dbName, conn->pguser);
-               if (conn->pgpass == NULL)
+               conn->pgpass = strdup(DefaultPassword);
+               if (!conn->pgpass)
+                       goto oom_error;
+               for (i = 0; i < conn->nconnhost; ++i)
                {
-                       conn->pgpass = strdup(DefaultPassword);
-                       if (!conn->pgpass)
-                               goto oom_error;
+                       conn->connhost[i].password =
+                               PasswordFromFile(conn->connhost[i].host,
+                                                                conn->connhost[i].port,
+                                                                conn->dbName, conn->pguser);
+                       if (conn->connhost[i].password != NULL)
+                               conn->dot_pgpass_used = true;
                }
-               else
-                       conn->dot_pgpass_used = true;
-       }
-
-       /*
-        * Allow unix socket specification in the host name
-        */
-       if (conn->pghost && is_absolute_path(conn->pghost))
-       {
-               if (conn->pgunixsocket)
-                       free(conn->pgunixsocket);
-               conn->pgunixsocket = conn->pghost;
-               conn->pghost = NULL;
        }
 
        /*
@@ -1142,6 +1278,7 @@ connectFailureMessage(PGconn *conn, int errorno)
        {
                char            host_addr[NI_MAXHOST];
                const char *displayed_host;
+               const char *displayed_port;
                struct sockaddr_storage *addr = &conn->raddr.addr;
 
                /*
@@ -1171,12 +1308,11 @@ connectFailureMessage(PGconn *conn, int errorno)
                else
                        strcpy(host_addr, "???");
 
-               if (conn->pghostaddr && conn->pghostaddr[0] != '\0')
-                       displayed_host = conn->pghostaddr;
-               else if (conn->pghost && conn->pghost[0] != '\0')
-                       displayed_host = conn->pghost;
-               else
-                       displayed_host = DefaultHost;
+               /* To which host and port were we actually connecting? */
+               displayed_host = conn->connhost[conn->whichhost].host;
+               displayed_port = conn->connhost[conn->whichhost].port;
+               if (displayed_port == NULL || displayed_port[0] == '\0')
+                       displayed_port = DEF_PGPORT_STR;
 
                /*
                 * If the user did not supply an IP address using 'hostaddr', and
@@ -1192,7 +1328,7 @@ connectFailureMessage(PGconn *conn, int errorno)
                                                          SOCK_STRERROR(errorno, sebuf, sizeof(sebuf)),
                                                          displayed_host,
                                                          host_addr,
-                                                         conn->pgport);
+                                                         displayed_port);
                else
                        appendPQExpBuffer(&conn->errorMessage,
                                                        libpq_gettext("could not connect to server: %s\n"
@@ -1200,7 +1336,7 @@ connectFailureMessage(PGconn *conn, int errorno)
                                                                           "\tTCP/IP connections on port %s?\n"),
                                                          SOCK_STRERROR(errorno, sebuf, sizeof(sebuf)),
                                                          displayed_host,
-                                                         conn->pgport);
+                                                         displayed_port);
        }
 }
 
@@ -1390,12 +1526,9 @@ setKeepalivesWin32(PGconn *conn)
 static int
 connectDBStart(PGconn *conn)
 {
-       int                     portnum;
        char            portstr[MAXPGPATH];
-       struct addrinfo *addrs = NULL;
-       struct addrinfo hint;
-       const char *node;
        int                     ret;
+       int                     i;
 
        if (!conn)
                return 0;
@@ -1408,83 +1541,86 @@ connectDBStart(PGconn *conn)
        conn->outCount = 0;
 
        /*
-        * Determine the parameters to pass to pg_getaddrinfo_all.
+        * Look up socket addresses for each possible host using
+        * pg_getaddrinfo_all.
         */
+       for (i = 0; i < conn->nconnhost; ++i)
+       {
+               pg_conn_host *ch = &conn->connhost[i];
+               char       *node = ch->host;
+               struct addrinfo hint;
+               int                     thisport;
 
-       /* Initialize hint structure */
-       MemSet(&hint, 0, sizeof(hint));
-       hint.ai_socktype = SOCK_STREAM;
-       hint.ai_family = AF_UNSPEC;
+               /* Initialize hint structure */
+               MemSet(&hint, 0, sizeof(hint));
+               hint.ai_socktype = SOCK_STREAM;
+               hint.ai_family = AF_UNSPEC;
 
-       /* Set up port number as a string */
-       if (conn->pgport != NULL && conn->pgport[0] != '\0')
-       {
-               portnum = atoi(conn->pgport);
-               if (portnum < 1 || portnum > 65535)
+               /* Figure out the port number we're going to use. */
+               if (ch->port == NULL)
+                       thisport = DEF_PGPORT;
+               else
                {
-                       appendPQExpBuffer(&conn->errorMessage,
+                       thisport = atoi(ch->port);
+                       if (thisport < 1 || thisport > 65535)
+                       {
+                               appendPQExpBuffer(&conn->errorMessage,
                                                          libpq_gettext("invalid port number: \"%s\"\n"),
-                                                         conn->pgport);
-                       conn->options_valid = false;
-                       goto connect_errReturn;
+                                                                 ch->port);
+                               conn->options_valid = false;
+                               goto connect_errReturn;
+                       }
                }
-       }
-       else
-               portnum = DEF_PGPORT;
-       snprintf(portstr, sizeof(portstr), "%d", portnum);
+               snprintf(portstr, sizeof(portstr), "%d", thisport);
 
-       if (conn->pghostaddr != NULL && conn->pghostaddr[0] != '\0')
-       {
-               /* Using pghostaddr avoids a hostname lookup */
-               node = 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;
-               hint.ai_family = AF_UNSPEC;
-       }
-       else
-       {
+               /* Set up for name resolution. */
+               switch (ch->type)
+               {
+                       case CHT_HOST_NAME:
+                               break;
+                       case CHT_HOST_ADDRESS:
+                               hint.ai_flags = AI_NUMERICHOST;
+                               break;
+                       case CHT_UNIX_SOCKET:
 #ifdef HAVE_UNIX_SOCKETS
-               /* pghostaddr and pghost are NULL, so use Unix domain socket */
-               node = NULL;
-               hint.ai_family = AF_UNIX;
-               UNIXSOCK_PATH(portstr, portnum, conn->pgunixsocket);
-               if (strlen(portstr) >= UNIXSOCK_PATH_BUFLEN)
+                               node = NULL;
+                               hint.ai_family = AF_UNIX;
+                               UNIXSOCK_PATH(portstr, thisport, ch->host);
+                               if (strlen(portstr) >= UNIXSOCK_PATH_BUFLEN)
+                               {
+                                       appendPQExpBuffer(&conn->errorMessage,
+                                                                         libpq_gettext("Unix-domain socket path \"%s\" is too long (maximum %d bytes)\n"),
+                                                                 portstr,
+                                                                 (int) (UNIXSOCK_PATH_BUFLEN - 1));
+                                       conn->options_valid = false;
+                                       goto connect_errReturn;
+                               }
+#else
+                               Assert(false);
+#endif
+                               break;
+               }
+
+               /* Use pg_getaddrinfo_all() to resolve the address */
+               ret = pg_getaddrinfo_all(node, portstr, &hint, &ch->addrlist);
+               if (ret || !ch->addrlist)
                {
-                       appendPQExpBuffer(&conn->errorMessage,
-                                                         libpq_gettext("Unix-domain socket path \"%s\" is too long (maximum %d bytes)\n"),
-                                                         portstr,
-                                                         (int) (UNIXSOCK_PATH_BUFLEN - 1));
+                       if (node)
+                               appendPQExpBuffer(&conn->errorMessage,
+                                                                 libpq_gettext("could not translate host name \"%s\" to address: %s\n"),
+                                                                 node, gai_strerror(ret));
+                       else
+                               appendPQExpBuffer(&conn->errorMessage,
+                                                                 libpq_gettext("could not translate Unix-domain socket path \"%s\" to address: %s\n"),
+                                                                 portstr, gai_strerror(ret));
+                       if (ch->addrlist)
+                       {
+                               pg_freeaddrinfo_all(hint.ai_family, ch->addrlist);
+                               ch->addrlist = NULL;
+                       }
                        conn->options_valid = false;
                        goto connect_errReturn;
                }
-#else
-               /* Without Unix sockets, default to localhost instead */
-               node = 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)
-       {
-               if (node)
-                       appendPQExpBuffer(&conn->errorMessage,
-                                                         libpq_gettext("could not translate host name \"%s\" to address: %s\n"),
-                                                         node, gai_strerror(ret));
-               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);
-               conn->options_valid = false;
-               goto connect_errReturn;
        }
 
 #ifdef USE_SSL
@@ -1498,9 +1634,8 @@ connectDBStart(PGconn *conn)
        /*
         * Set up to try to connect, with protocol 3.0 as the first attempt.
         */
-       conn->addrlist = addrs;
-       conn->addr_cur = addrs;
-       conn->addrlist_family = hint.ai_family;
+       conn->whichhost = 0;
+       conn->addr_cur = conn->connhost[0].addrlist;
        conn->pversion = PG_PROTOCOL(3, 0);
        conn->send_appname = true;
        conn->status = CONNECTION_NEEDED;
@@ -1702,11 +1837,27 @@ keep_going:                                             /* We will come back to here until there is
                                 * returned by pg_getaddrinfo_all().  conn->addr_cur is the
                                 * next one to try. We fail when we run out of addresses.
                                 */
-                               while (conn->addr_cur != NULL)
+                               for (;;)
                                {
-                                       struct addrinfo *addr_cur = conn->addr_cur;
+                                       struct addrinfo *addr_cur;
+
+                                       /*
+                                        * Advance to next possible host, if we've tried all of
+                                        * the addresses for the current host.
+                                        */
+                                       if (conn->addr_cur == NULL)
+                                       {
+                                               if (++conn->whichhost >= conn->nconnhost)
+                                               {
+                                                       conn->whichhost = 0;
+                                                       break;
+                                               }
+                                               conn->addr_cur =
+                                                       conn->connhost[conn->whichhost].addrlist;
+                                       }
 
                                        /* Remember current address for possible error msg */
+                                       addr_cur = conn->addr_cur;
                                        memcpy(&conn->raddr.addr, addr_cur->ai_addr,
                                                   addr_cur->ai_addrlen);
                                        conn->raddr.salen = addr_cur->ai_addrlen;
@@ -1718,7 +1869,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 (addr_cur->ai_next != NULL ||
+                                                       conn->whichhost + 1 < conn->nconnhost)
                                                {
                                                        conn->addr_cur = addr_cur->ai_next;
                                                        continue;
@@ -1944,7 +2096,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 (conn->addr_cur->ai_next != NULL ||
+                                               conn->whichhost + 1 < conn->nconnhost)
                                        {
                                                conn->addr_cur = conn->addr_cur->ai_next;
                                                conn->status = CONNECTION_NEEDED;
@@ -2599,9 +2752,25 @@ keep_going:                                              /* We will come back to here until there is
                                        goto error_return;
                                }
 
-                               /* We can release the address list now. */
-                               pg_freeaddrinfo_all(conn->addrlist_family, conn->addrlist);
-                               conn->addrlist = NULL;
+                               /* We can release the address lists now. */
+                               if (conn->connhost != NULL)
+                               {
+                                       int             i;
+
+                                       for (i = 0; i < conn->nconnhost; ++i)
+                                       {
+                                               int             family = AF_UNSPEC;
+
+#ifdef HAVE_UNIX_SOCKETS
+                                               if (conn->connhost[i].type == CHT_UNIX_SOCKET)
+                                                       family = AF_UNIX;
+#endif
+
+                                               pg_freeaddrinfo_all(family,
+                                                                                       conn->connhost[i].addrlist);
+                                               conn->connhost[i].addrlist = NULL;
+                                       }
+                               }
                                conn->addr_cur = NULL;
 
                                /* Fire up post-connection housekeeping if needed */
@@ -2858,6 +3027,21 @@ freePGconn(PGconn *conn)
                free(conn->events[i].name);
        }
 
+       /* clean up pg_conn_host structures */
+       if (conn->connhost != NULL)
+       {
+               for (i = 0; i < conn->nconnhost; ++i)
+               {
+                       if (conn->connhost[i].host != NULL)
+                               free(conn->connhost[i].host);
+                       if (conn->connhost[i].port != NULL)
+                               free(conn->connhost[i].port);
+                       if (conn->connhost[i].password != NULL)
+                               free(conn->connhost[i].password);
+               }
+               free(conn->connhost);
+       }
+
        if (conn->client_encoding_initial)
                free(conn->client_encoding_initial);
        if (conn->events)
@@ -2868,8 +3052,6 @@ freePGconn(PGconn *conn)
                free(conn->pghostaddr);
        if (conn->pgport)
                free(conn->pgport);
-       if (conn->pgunixsocket)
-               free(conn->pgunixsocket);
        if (conn->pgtty)
                free(conn->pgtty);
        if (conn->connect_timeout)
@@ -2983,8 +3165,24 @@ closePGconn(PGconn *conn)
        conn->asyncStatus = PGASYNC_IDLE;
        pqClearAsyncResult(conn);       /* deallocate result */
        resetPQExpBuffer(&conn->errorMessage);
-       pg_freeaddrinfo_all(conn->addrlist_family, conn->addrlist);
-       conn->addrlist = NULL;
+       if (conn->connhost != NULL)
+       {
+               int             i;
+
+               for (i = 0; i < conn->nconnhost; ++i)
+               {
+                       int             family = AF_UNSPEC;
+
+#ifdef HAVE_UNIX_SOCKETS
+                       if (conn->connhost[i].type == CHT_UNIX_SOCKET)
+                               family = AF_UNIX;
+#endif
+
+                       pg_freeaddrinfo_all(family,
+                                                               conn->connhost[i].addrlist);
+                       conn->connhost[i].addrlist = NULL;
+               }
+       }
        conn->addr_cur = NULL;
        notify = conn->notifyHead;
        while (notify != NULL)
@@ -4720,7 +4918,10 @@ conninfo_uri_parse(const char *uri, PQExpBuffer errorMessage,
  * postgresql://[user[:password]@][netloc][:port][/dbname][?param1=value1&...]
  *
  * where "netloc" is a hostname, an IPv4 address, or an IPv6 address surrounded
- * by literal square brackets.
+ * by literal square brackets.  As an extension, we also allow multiple
+ * netloc[:port] specifications, separated by commas:
+ *
+ * postgresql://[user[:password]@][netloc][:port][,...][/dbname][?param1=value1&...]
  *
  * Any of the URI parts might use percent-encoding (%xy).
  */
@@ -4736,6 +4937,17 @@ conninfo_uri_parse_options(PQconninfoOption *options, const char *uri,
        char       *user = NULL;
        char       *host = NULL;
        bool            retval = false;
+       PQExpBufferData hostbuf;
+       PQExpBufferData portbuf;
+
+       initPQExpBuffer(&hostbuf);
+       initPQExpBuffer(&portbuf);
+       if (PQExpBufferDataBroken(hostbuf) || PQExpBufferDataBroken(portbuf))
+       {
+               printfPQExpBuffer(errorMessage,
+                                                 libpq_gettext("out of memory\n"));
+               return false;
+       }
 
        /* need a modifiable copy of the input URI */
        buf = strdup(uri);
@@ -4810,85 +5022,104 @@ conninfo_uri_parse_options(PQconninfoOption *options, const char *uri,
        }
 
        /*
-        * "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.
+        * There may be multiple netloc[:port] pairs, each separated from the next
+        * by a comma.  When we initially enter this loop, "p" has been
+        * incremented past optional URI credential information at this point and
+        * now points at the "netloc" part of the URI.  On subsequent loop
+        * iterations, "p" has been incremented past the comma separator and now
+        * points at the start of the next "netloc".
         */
-       if (*p == '[')
+       for (;;)
        {
-               host = ++p;
-               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 == host)
-               {
-                       printfPQExpBuffer(errorMessage,
-                                                         libpq_gettext("IPv6 host address may not be empty in URI: \"%s\"\n"),
-                                                         uri);
-                       goto cleanup;
-               }
-
-               /* Cut off the bracket and advance */
-               *(p++) = '\0';
-
                /*
-                * The address may be followed by a port specifier or a slash or a
-                * query.
+                * Look for IPv6 address.
                 */
-               if (*p && *p != ':' && *p != '/' && *p != '?')
+               if (*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;
+                       host = ++p;
+                       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 == host)
+                       {
+                               printfPQExpBuffer(errorMessage,
+                                                                 libpq_gettext("IPv6 host address may not be empty in URI: \"%s\"\n"),
+                                                                 uri);
+                               goto cleanup;
+                       }
+
+                       /* Cut off the bracket and advance */
+                       *(p++) = '\0';
+
+                       /*
+                        * The address may be followed by a port specifier or a slash or a
+                        * query or a separator comma.
+                        */
+                       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;
+                       }
                }
-       }
-       else
-       {
-               /* not an IPv6 address: DNS-named or IPv4 netloc */
-               host = p;
+               else
+               {
+                       /* not an IPv6 address: DNS-named or IPv4 netloc */
+                       host = p;
 
-               /*
-                * Look for port specifier (colon) or end of host specifier (slash),
-                * or query (question mark).
-                */
-               while (*p && *p != ':' && *p != '/' && *p != '?')
-                       ++p;
-       }
+                       /*
+                        * Look for port specifier (colon) or end of host specifier (slash)
+                        * or query (question mark) or host separator (comma).
+                        */
+                       while (*p && *p != ':' && *p != '/' && *p != '?' && *p != ',')
+                               ++p;
+               }
 
-       /* Save the hostname terminator before we null it */
-       prevchar = *p;
-       *p = '\0';
+               /* Save the hostname terminator before we null it */
+               prevchar = *p;
+               *p = '\0';
 
-       if (*host &&
-               !conninfo_storeval(options, "host", host,
-                                                  errorMessage, false, true))
-               goto cleanup;
+               appendPQExpBufferStr(&hostbuf, host);
 
+               if (prevchar == ':')
+               {
+                       const char *port = ++p; /* advance past host terminator */
 
-       if (prevchar == ':')
-       {
-               const char *port = ++p; /* advance past host terminator */
+                       while (*p && *p != '/' && *p != '?' && *p != ',')
+                               ++p;
 
-               while (*p && *p != '/' && *p != '?')
-                       ++p;
+                       prevchar = *p;
+                       *p = '\0';
 
-               prevchar = *p;
-               *p = '\0';
+                       appendPQExpBufferStr(&portbuf, port);
+               }
 
-               if (*port &&
-                       !conninfo_storeval(options, "port", port,
-                                                          errorMessage, false, true))
-                       goto cleanup;
+               if (prevchar != ',')
+                       break;
+               ++p;                                            /* advance past comma separator */
+               appendPQExpBufferStr(&hostbuf, ",");
+               appendPQExpBufferStr(&portbuf, ",");
        }
 
+       /* Save final values for host and port. */
+       if (PQExpBufferDataBroken(hostbuf) || PQExpBufferDataBroken(portbuf))
+               goto cleanup;
+       if (hostbuf.data[0] &&
+               !conninfo_storeval(options, "host", hostbuf.data,
+                                                  errorMessage, false, true))
+               goto cleanup;
+       if (portbuf.data[0] &&
+               !conninfo_storeval(options, "port", portbuf.data,
+                                                  errorMessage, false, true))
+               goto cleanup;
+
        if (prevchar && prevchar != '?')
        {
                const char *dbname = ++p;               /* advance past host terminator */
@@ -4923,6 +5154,8 @@ conninfo_uri_parse_options(PQconninfoOption *options, const char *uri,
        retval = true;
 
 cleanup:
+       termPQExpBuffer(&hostbuf);
+       termPQExpBuffer(&portbuf);
        free(buf);
        return retval;
 }
@@ -5342,9 +5575,15 @@ PQuser(const PGconn *conn)
 char *
 PQpass(const PGconn *conn)
 {
+       char   *password = NULL;
+
        if (!conn)
                return NULL;
-       return conn->pgpass;
+       if (conn->connhost != NULL)
+               password = conn->connhost[conn->whichhost].password;
+       if (password == NULL)
+               password = conn->pgpass;
+       return password;
 }
 
 char *
@@ -5352,15 +5591,14 @@ PQhost(const PGconn *conn)
 {
        if (!conn)
                return NULL;
-       if (conn->pghost != NULL && conn->pghost[0] != '\0')
+       if (conn->connhost != NULL)
+               return conn->connhost[conn->whichhost].host;
+       else if (conn->pghost != NULL && conn->pghost[0] != '\0')
                return conn->pghost;
        else
        {
 #ifdef HAVE_UNIX_SOCKETS
-               if (conn->pgunixsocket != NULL && conn->pgunixsocket[0] != '\0')
-                       return conn->pgunixsocket;
-               else
-                       return DEFAULT_PGSOCKET_DIR;
+               return DEFAULT_PGSOCKET_DIR;
 #else
                return DefaultHost;
 #endif
@@ -5372,6 +5610,8 @@ PQport(const PGconn *conn)
 {
        if (!conn)
                return NULL;
+       if (conn->connhost != NULL)
+               return conn->connhost[conn->whichhost].port;
        return conn->pgport;
 }
 
@@ -5481,10 +5721,13 @@ PQbackendPID(const PGconn *conn)
 int
 PQconnectionNeedsPassword(const PGconn *conn)
 {
+       char   *password;
+
        if (!conn)
                return false;
+       password = PQpass(conn);
        if (conn->password_needed &&
-               (conn->pgpass == NULL || conn->pgpass[0] == '\0'))
+               (password == NULL || password[0] == '\0'))
                return true;
        else
                return false;
index 7007692fb1098f06172990f4f860d0b21817ed5a..854ec89924ba744fff4618847ae3abbcfe81dff2 100644 (file)
@@ -292,6 +292,30 @@ typedef struct pgDataValue
        const char *value;                      /* data value, without zero-termination */
 } PGdataValue;
 
+typedef enum pg_conn_host_type
+{
+       CHT_HOST_NAME,
+       CHT_HOST_ADDRESS,
+       CHT_UNIX_SOCKET
+} pg_conn_host_type;
+
+/*
+ * pg_conn_host stores all information about one of possibly several hosts
+ * mentioned in the connection string.  Derived by splitting the pghost
+ * on the comma character and then parsing each segment.
+ */
+typedef struct pg_conn_host
+{
+       char       *host;                       /* host name or address, or socket path */
+       pg_conn_host_type type;         /* type of host */
+       char       *port;                       /* port number for this host; if not NULL,
+                                                                * overrrides the PGConn's pgport */
+       char       *password;           /* password for this host, read from the
+                                                                * password file.  only set if the PGconn's
+                                                                * pgpass field is NULL. */
+       struct addrinfo *addrlist;      /* list of possible backend addresses */
+} pg_conn_host;
+
 /*
  * PGconn stores all the state data associated with a single connection
  * to a backend.
@@ -299,13 +323,15 @@ typedef struct pgDataValue
 struct pg_conn
 {
        /* Saved values of connection options */
-       char       *pghost;                     /* the machine on which the server is running */
+       char       *pghost;                     /* the machine on which the server is running,
+                                                                * or a path to a UNIX-domain socket, or a
+                                                                * comma-separated list of machines and/or
+                                                                * paths, optionally with port suffixes; if
+                                                                * NULL, use DEFAULT_PGSOCKET_DIR */
        char       *pghostaddr;         /* the numeric IP address of the machine on
                                                                 * which the server is running.  Takes
                                                                 * precedence over above. */
        char       *pgport;                     /* the server's communication port number */
-       char       *pgunixsocket;       /* the directory of the server's Unix-domain
-                                                                * socket; if NULL, use DEFAULT_PGSOCKET_DIR */
        char       *pgtty;                      /* tty on which the backend messages is
                                                                 * displayed (OBSOLETE, NOT USED) */
        char       *connect_timeout;    /* connection timeout (numeric string) */
@@ -363,6 +389,11 @@ struct pg_conn
        PGnotify   *notifyHead;         /* oldest unreported Notify msg */
        PGnotify   *notifyTail;         /* newest unreported Notify msg */
 
+       /* Support for multiple hosts in connection string */
+       int                     nconnhost;              /* # of possible hosts */
+       int                     whichhost;              /* host we're currently considering */
+       pg_conn_host *connhost;         /* details about each possible host */
+
        /* Connection data */
        pgsocket        sock;                   /* FD for socket, PGINVALID_SOCKET if
                                                                 * unconnected */
@@ -378,9 +409,7 @@ struct pg_conn
        bool            sigpipe_flag;   /* can we mask SIGPIPE via MSG_NOSIGNAL? */
 
        /* Transient state needed while establishing connection */
-       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 */
+       struct addrinfo *addr_cur;      /* backend address currently being tried */
        PGSetenvStatusType setenv_state;        /* for 2.0 protocol only */
        const PQEnvironmentOption *next_eo;
        bool            send_appname;   /* okay to send application_name? */