git.postgresql.org Git - postgresql.git/commitdiff (original) (raw)
Several libpq</> functions parse a user-specified string to obtain
connection parameters. There are two accepted formats for these strings:
plain keyword = value strings
- and RFC
- 3986 URIs.
+ and URIs. URIs generally follow
+ RFC
+ 3986, except that multi-host connection strings are allowed
+ as further described below.
The general form for a connection URI is:
-postgresql://[user[:password]@][netloc][:port][/dbname][?param1=value1&...]
+postgresql://[user[:password]@][netloc][:port][,...][/dbname][?param1=value1&...]
postgresql://user@localhost
postgresql://user:secret@localhost
postgresql://other@localhost/otherdb?connect_timeout=10&application_name=myapp
+postgresql://host1:123,host2:456/somedb
Components of the hierarchical part of the URI can also
be given as parameters. For example:
postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
+
+ It is possible to specify multiple host components, each with an optional
+ port component, in a single URI. A URI of the form
+ postgresql://host1:port1,host2:port2,host3:port3/
+ is equivalent to a connection string of the form
+ host=host1,host2,host3 port=port1,port2,port3. Each
+ host will be tried in turn until a connection is successfully established.
+
host
- Name of host to connect to.host name</></>
- If this begins with a slash, it specifies Unix-domain
+ Comma-separated list of host names.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 host 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 host is
+ not specified is to connect to a Unix-domain
socketUnix domain socket</></> in
/tmp (or whatever socket directory was specified
when PostgreSQL</> was built). On machines without
Port number to connect to at the server host, or socket file
name extension for Unix-domain
connections.port</></>
+ If the 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.
The following functions return parameter values established at connection.
- These values are fixed for the life of the PGconn</> object.
+ These values are fixed for the life of the connection. If a multi-host
+ connection string is used, the values of PQhost</>,
+ PQport</>, and PQpass</> can change if a new connection
+ is established using the same PGconn</> object. Other values
+ are fixed for the lifetime of the PGconn</> object.
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)
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
}
/*
- * 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;
}
/*
{
char host_addr[NI_MAXHOST];
const char *displayed_host;
+ const char *displayed_port;
struct sockaddr_storage *addr = &conn->raddr.addr;
/*
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
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"
"\tTCP/IP connections on port %s?\n"),
SOCK_STRERROR(errorno, sebuf, sizeof(sebuf)),
displayed_host,
- conn->pgport);
+ displayed_port);
}
}
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;
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
/*
* 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;
* 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;
* 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;
* 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;
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 */
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)
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)
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)
* 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).
*/
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);
}
/*
- * "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 */
retval = true;
cleanup:
+ termPQExpBuffer(&hostbuf);
+ termPQExpBuffer(&portbuf);
free(buf);
return retval;
}
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 *
{
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
{
if (!conn)
return NULL;
+ if (conn->connhost != NULL)
+ return conn->connhost[conn->whichhost].port;
return conn->pgport;
}
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;
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.
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) */
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 */
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? */