Commit 274bb2b3 authored by Robert Haas's avatar Robert Haas

libpq: Allow connection strings and URIs to specify multiple hosts.

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.
parent 77067106
...@@ -756,8 +756,10 @@ PGPing PQping(const char *conninfo); ...@@ -756,8 +756,10 @@ PGPing PQping(const char *conninfo);
Several <application>libpq</> functions parse a user-specified string to obtain Several <application>libpq</> functions parse a user-specified string to obtain
connection parameters. There are two accepted formats for these strings: connection parameters. There are two accepted formats for these strings:
plain <literal>keyword = value</literal> strings plain <literal>keyword = value</literal> strings
and <ulink url="http://www.ietf.org/rfc/rfc3986.txt">RFC and URIs. URIs generally follow
3986</ulink> URIs. <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> </para>
<sect3> <sect3>
...@@ -792,7 +794,7 @@ host=localhost port=5432 dbname=mydb connect_timeout=10 ...@@ -792,7 +794,7 @@ host=localhost port=5432 dbname=mydb connect_timeout=10
<para> <para>
The general form for a connection <acronym>URI</acronym> is: The general form for a connection <acronym>URI</acronym> is:
<synopsis> <synopsis>
postgresql://[user[:password]@][netloc][:port][/dbname][?param1=value1&amp;...] postgresql://[user[:password]@][netloc][:port][,...][/dbname][?param1=value1&amp;...]
</synopsis> </synopsis>
</para> </para>
...@@ -809,6 +811,7 @@ postgresql://localhost/mydb ...@@ -809,6 +811,7 @@ postgresql://localhost/mydb
postgresql://user@localhost postgresql://user@localhost
postgresql://user:secret@localhost postgresql://user:secret@localhost
postgresql://other@localhost/otherdb?connect_timeout=10&amp;application_name=myapp postgresql://other@localhost/otherdb?connect_timeout=10&amp;application_name=myapp
postgresql://host1:123,host2:456/somedb
</programlisting> </programlisting>
Components of the hierarchical part of the <acronym>URI</acronym> can also Components of the hierarchical part of the <acronym>URI</acronym> can also
be given as parameters. For example: be given as parameters. For example:
...@@ -856,6 +859,15 @@ postgresql:///dbname?host=/var/lib/postgresql ...@@ -856,6 +859,15 @@ postgresql:///dbname?host=/var/lib/postgresql
postgresql://%2Fvar%2Flib%2Fpostgresql/dbname postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
</programlisting> </programlisting>
</para> </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> </sect3>
</sect2> </sect2>
...@@ -870,12 +882,13 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname ...@@ -870,12 +882,13 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<term><literal>host</literal></term> <term><literal>host</literal></term>
<listitem> <listitem>
<para> <para>
Name of host to connect to.<indexterm><primary>host name</></> Comma-separated list of host names.<indexterm><primary>host name</></>
If this begins with a slash, it specifies Unix-domain If a host name begins with a slash, it specifies Unix-domain
communication rather than TCP/IP communication; the value is the communication rather than TCP/IP communication; the value is the
name of the directory in which the socket file is stored. The name of the directory in which the socket file is stored. If
default behavior when <literal>host</literal> is not specified multiple host names are specified, each will be tried in turn in
is to connect to a Unix-domain 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 socket<indexterm><primary>Unix domain socket</></> in
<filename>/tmp</filename> (or whatever socket directory was specified <filename>/tmp</filename> (or whatever socket directory was specified
when <productname>PostgreSQL</> was built). On machines without when <productname>PostgreSQL</> was built). On machines without
...@@ -950,6 +963,9 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname ...@@ -950,6 +963,9 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
Port number to connect to at the server host, or socket file Port number to connect to at the server host, or socket file
name extension for Unix-domain name extension for Unix-domain
connections.<indexterm><primary>port</></> 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> </para>
</listitem> </listitem>
</varlistentry> </varlistentry>
...@@ -1394,7 +1410,11 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname ...@@ -1394,7 +1410,11 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<para> <para>
The following functions return parameter values established at connection. 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> <variablelist>
<varlistentry id="libpq-pqdb"> <varlistentry id="libpq-pqdb">
......
...@@ -683,20 +683,26 @@ pg_fe_sendauth(AuthRequest areq, PGconn *conn) ...@@ -683,20 +683,26 @@ pg_fe_sendauth(AuthRequest areq, PGconn *conn)
case AUTH_REQ_MD5: case AUTH_REQ_MD5:
case AUTH_REQ_PASSWORD: case AUTH_REQ_PASSWORD:
{
char *password = conn->connhost[conn->whichhost].password;
if (password == NULL)
password = conn->pgpass;
conn->password_needed = true; conn->password_needed = true;
if (conn->pgpass == NULL || conn->pgpass[0] == '\0') if (password == NULL || password[0] == '\0')
{ {
printfPQExpBuffer(&conn->errorMessage, printfPQExpBuffer(&conn->errorMessage,
PQnoPasswordSupplied); PQnoPasswordSupplied);
return STATUS_ERROR; return STATUS_ERROR;
} }
if (pg_password_sendauth(conn, conn->pgpass, areq) != STATUS_OK) if (pg_password_sendauth(conn, password, areq) != STATUS_OK)
{ {
printfPQExpBuffer(&conn->errorMessage, printfPQExpBuffer(&conn->errorMessage,
"fe_sendauth: error sending password authentication\n"); "fe_sendauth: error sending password authentication\n");
return STATUS_ERROR; return STATUS_ERROR;
} }
break; break;
}
case AUTH_REQ_SCM_CREDS: case AUTH_REQ_SCM_CREDS:
if (pg_local_sendauth(conn) != STATUS_OK) if (pg_local_sendauth(conn) != STATUS_OK)
......
...@@ -770,6 +770,148 @@ connectOptions1(PGconn *conn, const char *conninfo) ...@@ -770,6 +770,148 @@ connectOptions1(PGconn *conn, const char *conninfo)
static bool static bool
connectOptions2(PGconn *conn) 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 * 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 * fail, since the only way we get here is if pg_fe_getauthname() failed
...@@ -800,33 +942,27 @@ connectOptions2(PGconn *conn) ...@@ -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') if (conn->pgpass == NULL || conn->pgpass[0] == '\0')
{ {
int i;
if (conn->pgpass) if (conn->pgpass)
free(conn->pgpass); free(conn->pgpass);
conn->pgpass = PasswordFromFile(conn->pghost, conn->pgport,
conn->dbName, conn->pguser);
if (conn->pgpass == NULL)
{
conn->pgpass = strdup(DefaultPassword); conn->pgpass = strdup(DefaultPassword);
if (!conn->pgpass) if (!conn->pgpass)
goto oom_error; goto oom_error;
} for (i = 0; i < conn->nconnhost; ++i)
else {
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; 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) ...@@ -1142,6 +1278,7 @@ connectFailureMessage(PGconn *conn, int errorno)
{ {
char host_addr[NI_MAXHOST]; char host_addr[NI_MAXHOST];
const char *displayed_host; const char *displayed_host;
const char *displayed_port;
struct sockaddr_storage *addr = &conn->raddr.addr; struct sockaddr_storage *addr = &conn->raddr.addr;
/* /*
...@@ -1171,12 +1308,11 @@ connectFailureMessage(PGconn *conn, int errorno) ...@@ -1171,12 +1308,11 @@ connectFailureMessage(PGconn *conn, int errorno)
else else
strcpy(host_addr, "???"); strcpy(host_addr, "???");
if (conn->pghostaddr && conn->pghostaddr[0] != '\0') /* To which host and port were we actually connecting? */
displayed_host = conn->pghostaddr; displayed_host = conn->connhost[conn->whichhost].host;
else if (conn->pghost && conn->pghost[0] != '\0') displayed_port = conn->connhost[conn->whichhost].port;
displayed_host = conn->pghost; if (displayed_port == NULL || displayed_port[0] == '\0')
else displayed_port = DEF_PGPORT_STR;
displayed_host = DefaultHost;
/* /*
* If the user did not supply an IP address using 'hostaddr', and * If the user did not supply an IP address using 'hostaddr', and
...@@ -1192,7 +1328,7 @@ connectFailureMessage(PGconn *conn, int errorno) ...@@ -1192,7 +1328,7 @@ connectFailureMessage(PGconn *conn, int errorno)
SOCK_STRERROR(errorno, sebuf, sizeof(sebuf)), SOCK_STRERROR(errorno, sebuf, sizeof(sebuf)),
displayed_host, displayed_host,
host_addr, host_addr,
conn->pgport); displayed_port);
else else
appendPQExpBuffer(&conn->errorMessage, appendPQExpBuffer(&conn->errorMessage,
libpq_gettext("could not connect to server: %s\n" libpq_gettext("could not connect to server: %s\n"
...@@ -1200,7 +1336,7 @@ connectFailureMessage(PGconn *conn, int errorno) ...@@ -1200,7 +1336,7 @@ connectFailureMessage(PGconn *conn, int errorno)
"\tTCP/IP connections on port %s?\n"), "\tTCP/IP connections on port %s?\n"),
SOCK_STRERROR(errorno, sebuf, sizeof(sebuf)), SOCK_STRERROR(errorno, sebuf, sizeof(sebuf)),
displayed_host, displayed_host,
conn->pgport); displayed_port);
} }
} }
...@@ -1390,12 +1526,9 @@ setKeepalivesWin32(PGconn *conn) ...@@ -1390,12 +1526,9 @@ setKeepalivesWin32(PGconn *conn)
static int static int
connectDBStart(PGconn *conn) connectDBStart(PGconn *conn)
{ {
int portnum;
char portstr[MAXPGPATH]; char portstr[MAXPGPATH];
struct addrinfo *addrs = NULL;
struct addrinfo hint;
const char *node;
int ret; int ret;
int i;
if (!conn) if (!conn)
return 0; return 0;
...@@ -1408,51 +1541,51 @@ connectDBStart(PGconn *conn) ...@@ -1408,51 +1541,51 @@ connectDBStart(PGconn *conn)
conn->outCount = 0; 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 */ /* Initialize hint structure */
MemSet(&hint, 0, sizeof(hint)); MemSet(&hint, 0, sizeof(hint));
hint.ai_socktype = SOCK_STREAM; hint.ai_socktype = SOCK_STREAM;
hint.ai_family = AF_UNSPEC; hint.ai_family = AF_UNSPEC;
/* Set up port number as a string */ /* Figure out the port number we're going to use. */
if (conn->pgport != NULL && conn->pgport[0] != '\0') if (ch->port == NULL)
thisport = DEF_PGPORT;
else
{ {
portnum = atoi(conn->pgport); thisport = atoi(ch->port);
if (portnum < 1 || portnum > 65535) if (thisport < 1 || thisport > 65535)
{ {
appendPQExpBuffer(&conn->errorMessage, appendPQExpBuffer(&conn->errorMessage,
libpq_gettext("invalid port number: \"%s\"\n"), libpq_gettext("invalid port number: \"%s\"\n"),
conn->pgport); ch->port);
conn->options_valid = false; conn->options_valid = false;
goto connect_errReturn; goto connect_errReturn;
} }
} }
else snprintf(portstr, sizeof(portstr), "%d", thisport);
portnum = DEF_PGPORT;
snprintf(portstr, sizeof(portstr), "%d", portnum);
if (conn->pghostaddr != NULL && conn->pghostaddr[0] != '\0') /* Set up for name resolution. */
switch (ch->type)
{ {
/* Using pghostaddr avoids a hostname lookup */ case CHT_HOST_NAME:
node = conn->pghostaddr; break;
hint.ai_family = AF_UNSPEC; case CHT_HOST_ADDRESS:
hint.ai_flags = AI_NUMERICHOST; hint.ai_flags = AI_NUMERICHOST;
} break;
else if (conn->pghost != NULL && conn->pghost[0] != '\0') case CHT_UNIX_SOCKET:
{
/* Using pghost, so we have to look-up the hostname */
node = conn->pghost;
hint.ai_family = AF_UNSPEC;
}
else
{
#ifdef HAVE_UNIX_SOCKETS #ifdef HAVE_UNIX_SOCKETS
/* pghostaddr and pghost are NULL, so use Unix domain socket */
node = NULL; node = NULL;
hint.ai_family = AF_UNIX; hint.ai_family = AF_UNIX;
UNIXSOCK_PATH(portstr, portnum, conn->pgunixsocket); UNIXSOCK_PATH(portstr, thisport, ch->host);
if (strlen(portstr) >= UNIXSOCK_PATH_BUFLEN) if (strlen(portstr) >= UNIXSOCK_PATH_BUFLEN)
{ {
appendPQExpBuffer(&conn->errorMessage, appendPQExpBuffer(&conn->errorMessage,
...@@ -1463,15 +1596,14 @@ connectDBStart(PGconn *conn) ...@@ -1463,15 +1596,14 @@ connectDBStart(PGconn *conn)
goto connect_errReturn; goto connect_errReturn;
} }
#else #else
/* Without Unix sockets, default to localhost instead */ Assert(false);
node = DefaultHost; #endif
hint.ai_family = AF_UNSPEC; break;
#endif /* HAVE_UNIX_SOCKETS */
} }
/* Use pg_getaddrinfo_all() to resolve the address */ /* Use pg_getaddrinfo_all() to resolve the address */
ret = pg_getaddrinfo_all(node, portstr, &hint, &addrs); ret = pg_getaddrinfo_all(node, portstr, &hint, &ch->addrlist);
if (ret || !addrs) if (ret || !ch->addrlist)
{ {
if (node) if (node)
appendPQExpBuffer(&conn->errorMessage, appendPQExpBuffer(&conn->errorMessage,
...@@ -1481,11 +1613,15 @@ connectDBStart(PGconn *conn) ...@@ -1481,11 +1613,15 @@ connectDBStart(PGconn *conn)
appendPQExpBuffer(&conn->errorMessage, appendPQExpBuffer(&conn->errorMessage,
libpq_gettext("could not translate Unix-domain socket path \"%s\" to address: %s\n"), libpq_gettext("could not translate Unix-domain socket path \"%s\" to address: %s\n"),
portstr, gai_strerror(ret)); portstr, gai_strerror(ret));
if (addrs) if (ch->addrlist)
pg_freeaddrinfo_all(hint.ai_family, addrs); {
pg_freeaddrinfo_all(hint.ai_family, ch->addrlist);
ch->addrlist = NULL;
}
conn->options_valid = false; conn->options_valid = false;
goto connect_errReturn; goto connect_errReturn;
} }
}
#ifdef USE_SSL #ifdef USE_SSL
/* setup values based on SSL mode */ /* setup values based on SSL mode */
...@@ -1498,9 +1634,8 @@ connectDBStart(PGconn *conn) ...@@ -1498,9 +1634,8 @@ connectDBStart(PGconn *conn)
/* /*
* Set up to try to connect, with protocol 3.0 as the first attempt. * Set up to try to connect, with protocol 3.0 as the first attempt.
*/ */
conn->addrlist = addrs; conn->whichhost = 0;
conn->addr_cur = addrs; conn->addr_cur = conn->connhost[0].addrlist;
conn->addrlist_family = hint.ai_family;
conn->pversion = PG_PROTOCOL(3, 0); conn->pversion = PG_PROTOCOL(3, 0);
conn->send_appname = true; conn->send_appname = true;
conn->status = CONNECTION_NEEDED; conn->status = CONNECTION_NEEDED;
...@@ -1702,11 +1837,27 @@ keep_going: /* We will come back to here until there is ...@@ -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 * returned by pg_getaddrinfo_all(). conn->addr_cur is the
* next one to try. We fail when we run out of addresses. * 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 */ /* Remember current address for possible error msg */
addr_cur = conn->addr_cur;
memcpy(&conn->raddr.addr, addr_cur->ai_addr, memcpy(&conn->raddr.addr, addr_cur->ai_addr,
addr_cur->ai_addrlen); addr_cur->ai_addrlen);
conn->raddr.salen = 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 ...@@ -1718,7 +1869,8 @@ keep_going: /* We will come back to here until there is
* ignore socket() failure if we have more addresses * ignore socket() failure if we have more addresses
* to try * 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; conn->addr_cur = addr_cur->ai_next;
continue; continue;
...@@ -1944,7 +2096,8 @@ keep_going: /* We will come back to here until there is ...@@ -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 * If more addresses remain, keep trying, just as in the
* case where connect() returned failure immediately. * 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->addr_cur = conn->addr_cur->ai_next;
conn->status = CONNECTION_NEEDED; conn->status = CONNECTION_NEEDED;
...@@ -2599,9 +2752,25 @@ keep_going: /* We will come back to here until there is ...@@ -2599,9 +2752,25 @@ keep_going: /* We will come back to here until there is
goto error_return; goto error_return;
} }
/* We can release the address list now. */ /* We can release the address lists now. */
pg_freeaddrinfo_all(conn->addrlist_family, conn->addrlist); if (conn->connhost != NULL)
conn->addrlist = 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; conn->addr_cur = NULL;
/* Fire up post-connection housekeeping if needed */ /* Fire up post-connection housekeeping if needed */
...@@ -2858,6 +3027,21 @@ freePGconn(PGconn *conn) ...@@ -2858,6 +3027,21 @@ freePGconn(PGconn *conn)
free(conn->events[i].name); 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) if (conn->client_encoding_initial)
free(conn->client_encoding_initial); free(conn->client_encoding_initial);
if (conn->events) if (conn->events)
...@@ -2868,8 +3052,6 @@ freePGconn(PGconn *conn) ...@@ -2868,8 +3052,6 @@ freePGconn(PGconn *conn)
free(conn->pghostaddr); free(conn->pghostaddr);
if (conn->pgport) if (conn->pgport)
free(conn->pgport); free(conn->pgport);
if (conn->pgunixsocket)
free(conn->pgunixsocket);
if (conn->pgtty) if (conn->pgtty)
free(conn->pgtty); free(conn->pgtty);
if (conn->connect_timeout) if (conn->connect_timeout)
...@@ -2983,8 +3165,24 @@ closePGconn(PGconn *conn) ...@@ -2983,8 +3165,24 @@ closePGconn(PGconn *conn)
conn->asyncStatus = PGASYNC_IDLE; conn->asyncStatus = PGASYNC_IDLE;
pqClearAsyncResult(conn); /* deallocate result */ pqClearAsyncResult(conn); /* deallocate result */
resetPQExpBuffer(&conn->errorMessage); resetPQExpBuffer(&conn->errorMessage);
pg_freeaddrinfo_all(conn->addrlist_family, conn->addrlist); if (conn->connhost != NULL)
conn->addrlist = 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; conn->addr_cur = NULL;
notify = conn->notifyHead; notify = conn->notifyHead;
while (notify != NULL) while (notify != NULL)
...@@ -4720,7 +4918,10 @@ conninfo_uri_parse(const char *uri, PQExpBuffer errorMessage, ...@@ -4720,7 +4918,10 @@ conninfo_uri_parse(const char *uri, PQExpBuffer errorMessage,
* postgresql://[user[:password]@][netloc][:port][/dbname][?param1=value1&...] * postgresql://[user[:password]@][netloc][:port][/dbname][?param1=value1&...]
* *
* where "netloc" is a hostname, an IPv4 address, or an IPv6 address surrounded * 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). * Any of the URI parts might use percent-encoding (%xy).
*/ */
...@@ -4736,6 +4937,17 @@ conninfo_uri_parse_options(PQconninfoOption *options, const char *uri, ...@@ -4736,6 +4937,17 @@ conninfo_uri_parse_options(PQconninfoOption *options, const char *uri,
char *user = NULL; char *user = NULL;
char *host = NULL; char *host = NULL;
bool retval = false; 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 */ /* need a modifiable copy of the input URI */
buf = strdup(uri); buf = strdup(uri);
...@@ -4810,9 +5022,16 @@ conninfo_uri_parse_options(PQconninfoOption *options, const char *uri, ...@@ -4810,9 +5022,16 @@ conninfo_uri_parse_options(PQconninfoOption *options, const char *uri,
} }
/* /*
* "p" has been incremented past optional URI credential information at * There may be multiple netloc[:port] pairs, each separated from the next
* this point and now points at the "netloc" part of the URI. * 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".
*/
for (;;)
{
/*
* Look for IPv6 address. * Look for IPv6 address.
*/ */
if (*p == '[') if (*p == '[')
...@@ -4840,9 +5059,9 @@ conninfo_uri_parse_options(PQconninfoOption *options, const char *uri, ...@@ -4840,9 +5059,9 @@ conninfo_uri_parse_options(PQconninfoOption *options, const char *uri,
/* /*
* The address may be followed by a port specifier or a slash or a * The address may be followed by a port specifier or a slash or a
* query. * query or a separator comma.
*/ */
if (*p && *p != ':' && *p != '/' && *p != '?') if (*p && *p != ':' && *p != '/' && *p != '?' && *p != ',')
{ {
printfPQExpBuffer(errorMessage, printfPQExpBuffer(errorMessage,
libpq_gettext("unexpected character \"%c\" at position %d in URI (expected \":\" or \"/\"): \"%s\"\n"), libpq_gettext("unexpected character \"%c\" at position %d in URI (expected \":\" or \"/\"): \"%s\"\n"),
...@@ -4856,10 +5075,10 @@ conninfo_uri_parse_options(PQconninfoOption *options, const char *uri, ...@@ -4856,10 +5075,10 @@ conninfo_uri_parse_options(PQconninfoOption *options, const char *uri,
host = p; host = p;
/* /*
* Look for port specifier (colon) or end of host specifier (slash), * Look for port specifier (colon) or end of host specifier (slash)
* or query (question mark). * or query (question mark) or host separator (comma).
*/ */
while (*p && *p != ':' && *p != '/' && *p != '?') while (*p && *p != ':' && *p != '/' && *p != '?' && *p != ',')
++p; ++p;
} }
...@@ -4867,27 +5086,39 @@ conninfo_uri_parse_options(PQconninfoOption *options, const char *uri, ...@@ -4867,27 +5086,39 @@ conninfo_uri_parse_options(PQconninfoOption *options, const char *uri,
prevchar = *p; prevchar = *p;
*p = '\0'; *p = '\0';
if (*host && appendPQExpBufferStr(&hostbuf, host);
!conninfo_storeval(options, "host", host,
errorMessage, false, true))
goto cleanup;
if (prevchar == ':') if (prevchar == ':')
{ {
const char *port = ++p; /* advance past host terminator */ const char *port = ++p; /* advance past host terminator */
while (*p && *p != '/' && *p != '?') while (*p && *p != '/' && *p != '?' && *p != ',')
++p; ++p;
prevchar = *p; prevchar = *p;
*p = '\0'; *p = '\0';
if (*port && appendPQExpBufferStr(&portbuf, port);
!conninfo_storeval(options, "port", port, }
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)) errorMessage, false, true))
goto cleanup; goto cleanup;
}
if (prevchar && prevchar != '?') if (prevchar && prevchar != '?')
{ {
...@@ -4923,6 +5154,8 @@ conninfo_uri_parse_options(PQconninfoOption *options, const char *uri, ...@@ -4923,6 +5154,8 @@ conninfo_uri_parse_options(PQconninfoOption *options, const char *uri,
retval = true; retval = true;
cleanup: cleanup:
termPQExpBuffer(&hostbuf);
termPQExpBuffer(&portbuf);
free(buf); free(buf);
return retval; return retval;
} }
...@@ -5342,9 +5575,15 @@ PQuser(const PGconn *conn) ...@@ -5342,9 +5575,15 @@ PQuser(const PGconn *conn)
char * char *
PQpass(const PGconn *conn) PQpass(const PGconn *conn)
{ {
char *password = NULL;
if (!conn) if (!conn)
return NULL; return NULL;
return conn->pgpass; if (conn->connhost != NULL)
password = conn->connhost[conn->whichhost].password;
if (password == NULL)
password = conn->pgpass;
return password;
} }
char * char *
...@@ -5352,14 +5591,13 @@ PQhost(const PGconn *conn) ...@@ -5352,14 +5591,13 @@ PQhost(const PGconn *conn)
{ {
if (!conn) if (!conn)
return NULL; 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; return conn->pghost;
else else
{ {
#ifdef HAVE_UNIX_SOCKETS #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 #else
return DefaultHost; return DefaultHost;
...@@ -5372,6 +5610,8 @@ PQport(const PGconn *conn) ...@@ -5372,6 +5610,8 @@ PQport(const PGconn *conn)
{ {
if (!conn) if (!conn)
return NULL; return NULL;
if (conn->connhost != NULL)
return conn->connhost[conn->whichhost].port;
return conn->pgport; return conn->pgport;
} }
...@@ -5481,10 +5721,13 @@ PQbackendPID(const PGconn *conn) ...@@ -5481,10 +5721,13 @@ PQbackendPID(const PGconn *conn)
int int
PQconnectionNeedsPassword(const PGconn *conn) PQconnectionNeedsPassword(const PGconn *conn)
{ {
char *password;
if (!conn) if (!conn)
return false; return false;
password = PQpass(conn);
if (conn->password_needed && if (conn->password_needed &&
(conn->pgpass == NULL || conn->pgpass[0] == '\0')) (password == NULL || password[0] == '\0'))
return true; return true;
else else
return false; return false;
......
...@@ -292,6 +292,30 @@ typedef struct pgDataValue ...@@ -292,6 +292,30 @@ typedef struct pgDataValue
const char *value; /* data value, without zero-termination */ const char *value; /* data value, without zero-termination */
} PGdataValue; } 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 * PGconn stores all the state data associated with a single connection
* to a backend. * to a backend.
...@@ -299,13 +323,15 @@ typedef struct pgDataValue ...@@ -299,13 +323,15 @@ typedef struct pgDataValue
struct pg_conn struct pg_conn
{ {
/* Saved values of connection options */ /* 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 char *pghostaddr; /* the numeric IP address of the machine on
* which the server is running. Takes * which the server is running. Takes
* precedence over above. */ * precedence over above. */
char *pgport; /* the server's communication port number */ 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 char *pgtty; /* tty on which the backend messages is
* displayed (OBSOLETE, NOT USED) */ * displayed (OBSOLETE, NOT USED) */
char *connect_timeout; /* connection timeout (numeric string) */ char *connect_timeout; /* connection timeout (numeric string) */
...@@ -363,6 +389,11 @@ struct pg_conn ...@@ -363,6 +389,11 @@ struct pg_conn
PGnotify *notifyHead; /* oldest unreported Notify msg */ PGnotify *notifyHead; /* oldest unreported Notify msg */
PGnotify *notifyTail; /* newest 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 */ /* Connection data */
pgsocket sock; /* FD for socket, PGINVALID_SOCKET if pgsocket sock; /* FD for socket, PGINVALID_SOCKET if
* unconnected */ * unconnected */
...@@ -378,9 +409,7 @@ struct pg_conn ...@@ -378,9 +409,7 @@ struct pg_conn
bool sigpipe_flag; /* can we mask SIGPIPE via MSG_NOSIGNAL? */ bool sigpipe_flag; /* can we mask SIGPIPE via MSG_NOSIGNAL? */
/* Transient state needed while establishing connection */ /* Transient state needed while establishing connection */
struct addrinfo *addrlist; /* list of possible backend addresses */ struct addrinfo *addr_cur; /* backend address currently being tried */
struct addrinfo *addr_cur; /* the one currently being tried */
int addrlist_family; /* needed to know how to free addrlist */
PGSetenvStatusType setenv_state; /* for 2.0 protocol only */ PGSetenvStatusType setenv_state; /* for 2.0 protocol only */
const PQEnvironmentOption *next_eo; const PQEnvironmentOption *next_eo;
bool send_appname; /* okay to send application_name? */ bool send_appname; /* okay to send application_name? */
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment