<para>
Establishes a new connection to a <productname>PostgreSQL</productname>
server. The connection parameters to use can be specified either
- using a positional syntax, or using <replaceable>conninfo</replaceable> connection
- strings as detailed in <xref linkend="libpq-connstring"/>.
+ using a positional syntax (one or more of database name, user,
+ host, and port), or using a <replaceable>conninfo</replaceable>
+ connection string as detailed in
+ <xref linkend="libpq-connstring"/>. If no arguments are given, a
+ new connection is made using the same parameters as before.
</para>
<para>
- Where the command omits database name, user, host, or port, the new
- connection can reuse values from the previous connection. By default,
- values from the previous connection are reused except when processing
- a <replaceable>conninfo</replaceable> string. Passing a first argument
- of <literal>-reuse-previous=on</literal>
- or <literal>-reuse-previous=off</literal> overrides that default.
- When the command neither specifies nor reuses a particular parameter,
- the <application>libpq</application> default is used. Specifying any
+ Specifying any
of <replaceable class="parameter">dbname</replaceable>,
<replaceable class="parameter">username</replaceable>,
<replaceable class="parameter">host</replaceable> or
<replaceable class="parameter">port</replaceable>
as <literal>-</literal> is equivalent to omitting that parameter.
- If <literal>hostaddr</literal> was specified in the original
- connection's <structname>conninfo</structname>, that address is reused
- for the new connection (disregarding any other host specification).
+ </para>
+
+ <para>
+ The new connection can re-use connection parameters from the previous
+ connection; not only database name, user, host, and port, but other
+ settings such as <replaceable>sslmode</replaceable>. By default,
+ parameters are re-used in the positional syntax, but not when
+ a <replaceable>conninfo</replaceable> string is given. Passing a
+ first argument of <literal>-reuse-previous=on</literal>
+ or <literal>-reuse-previous=off</literal> overrides that default. If
+ parameters are re-used, then any parameter not explicitly specified as
+ a positional parameter or in the <replaceable>conninfo</replaceable>
+ string is taken from the existing connection's parameters. An
+ exception is that if the <replaceable>host</replaceable> setting
+ is changed from its previous value using the positional syntax,
+ any <replaceable>hostaddr</replaceable> setting present in the
+ existing connection's parameters is dropped.
+ When the command neither specifies nor reuses a particular parameter,
+ the <application>libpq</application> default is used.
</para>
<para>
If the new connection is successfully made, the previous
connection is closed.
- If the connection attempt failed (wrong user name, access
- denied, etc.), the previous connection will only be kept if
- <application>psql</application> is in interactive mode. When
+ If the connection attempt fails (wrong user name, access
+ denied, etc.), the previous connection will be kept if
+ <application>psql</application> is in interactive mode. But when
executing a non-interactive script, processing will
immediately stop with an error. This distinction was chosen as
a user convenience against typos on the one hand, and a safety
=> \c mydb myuser host.dom 6432
=> \c service=foo
=> \c "host=localhost port=5432 dbname=mydb connect_timeout=10 sslmode=disable"
+=> \c -reuse-previous=on sslmode=require -- changes only sslmode
=> \c postgresql://tom@localhost/mydb?application_name=myapp
</programlisting>
</listitem>
return false;
}
-/* return whether the connection has 'hostaddr' in its conninfo */
-static bool
-has_hostaddr(PGconn *conn)
-{
- bool used = false;
- PQconninfoOption *ciopt = PQconninfo(conn);
-
- for (PQconninfoOption *p = ciopt; p->keyword != NULL; p++)
- {
- if (strcmp(p->keyword, "hostaddr") == 0 && p->val != NULL)
- {
- used = true;
- break;
- }
- }
-
- PQconninfoFree(ciopt);
- return used;
-}
-
/*
* do_connect -- handler for \connect
*
char *dbname, char *user, char *host, char *port)
{
PGconn *o_conn = pset.db,
- *n_conn;
+ *n_conn = NULL;
+ PQconninfoOption *cinfo;
+ int nconnopts = 0;
+ bool same_host = false;
char *password = NULL;
- char *hostaddr = NULL;
- bool keep_password;
+ bool success = true;
+ bool keep_password = true;
bool has_connection_string;
bool reuse_previous;
- PQExpBufferData connstr;
if (!o_conn && (!dbname || !user || !host || !port))
{
}
/*
- * Grab missing values from the old connection. If we grab host (or host
- * is the same as before) and hostaddr was set, grab that too.
+ * If we intend to re-use connection parameters, collect them out of the
+ * old connection, then replace individual values as necessary. Otherwise,
+ * obtain a PQconninfoOption array containing libpq's defaults, and modify
+ * that. Note this function assumes that PQconninfo, PQconndefaults, and
+ * PQconninfoParse will all produce arrays containing the same options in
+ * the same order.
*/
if (reuse_previous)
+ cinfo = PQconninfo(o_conn);
+ else
+ cinfo = PQconndefaults();
+
+ if (cinfo)
{
- if (!user)
- user = PQuser(o_conn);
- if (host && strcmp(host, PQhost(o_conn)) == 0 &&
- has_hostaddr(o_conn))
+ if (has_connection_string)
{
- hostaddr = PQhostaddr(o_conn);
+ /* Parse the connstring and insert values into cinfo */
+ PQconninfoOption *replcinfo;
+ char *errmsg;
+
+ replcinfo = PQconninfoParse(dbname, &errmsg);
+ if (replcinfo)
+ {
+ PQconninfoOption *ci;
+ PQconninfoOption *replci;
+
+ for (ci = cinfo, replci = replcinfo;
+ ci->keyword && replci->keyword;
+ ci++, replci++)
+ {
+ Assert(strcmp(ci->keyword, replci->keyword) == 0);
+ /* Insert value from connstring if one was provided */
+ if (replci->val)
+ {
+ /*
+ * We know that both val strings were allocated by
+ * libpq, so the least messy way to avoid memory leaks
+ * is to swap them.
+ */
+ char *swap = replci->val;
+
+ replci->val = ci->val;
+ ci->val = swap;
+ }
+ }
+ Assert(ci->keyword == NULL && replci->keyword == NULL);
+
+ /* While here, determine how many option slots there are */
+ nconnopts = ci - cinfo;
+
+ PQconninfoFree(replcinfo);
+
+ /* We never re-use a password with a conninfo string. */
+ keep_password = false;
+
+ /* Don't let code below try to inject dbname into params. */
+ dbname = NULL;
+ }
+ else
+ {
+ /* PQconninfoParse failed */
+ if (errmsg)
+ {
+ pg_log_error("%s", errmsg);
+ PQfreemem(errmsg);
+ }
+ else
+ pg_log_error("out of memory");
+ success = false;
+ }
}
- if (!host)
+ else
{
- host = PQhost(o_conn);
- if (has_hostaddr(o_conn))
- hostaddr = PQhostaddr(o_conn);
+ /*
+ * If dbname isn't a connection string, then we'll inject it and
+ * the other parameters into the keyword array below. (We can't
+ * easily insert them into the cinfo array because of memory
+ * management issues: PQconninfoFree would misbehave on Windows.)
+ * However, to avoid dependencies on the order in which parameters
+ * appear in the array, make a preliminary scan to set
+ * keep_password and same_host correctly.
+ *
+ * While any change in user, host, or port causes us to ignore the
+ * old connection's password, we don't force that for dbname,
+ * since passwords aren't database-specific.
+ */
+ PQconninfoOption *ci;
+
+ for (ci = cinfo; ci->keyword; ci++)
+ {
+ if (user && strcmp(ci->keyword, "user") == 0)
+ {
+ if (!(ci->val && strcmp(user, ci->val) == 0))
+ keep_password = false;
+ }
+ else if (host && strcmp(ci->keyword, "host") == 0)
+ {
+ if (ci->val && strcmp(host, ci->val) == 0)
+ same_host = true;
+ else
+ keep_password = false;
+ }
+ else if (port && strcmp(ci->keyword, "port") == 0)
+ {
+ if (!(ci->val && strcmp(port, ci->val) == 0))
+ keep_password = false;
+ }
+ }
+
+ /* While here, determine how many option slots there are */
+ nconnopts = ci - cinfo;
}
- if (!port)
- port = PQport(o_conn);
}
-
- /*
- * Any change in the parameters read above makes us discard the password.
- * We also discard it if we're to use a conninfo rather than the
- * positional syntax.
- */
- if (has_connection_string)
- keep_password = false;
else
- keep_password =
- (user && PQuser(o_conn) && strcmp(user, PQuser(o_conn)) == 0) &&
- (host && PQhost(o_conn) && strcmp(host, PQhost(o_conn)) == 0) &&
- (port && PQport(o_conn) && strcmp(port, PQport(o_conn)) == 0);
-
- /*
- * Grab missing dbname from old connection. No password discard if this
- * changes: passwords aren't (usually) database-specific.
- */
- if (!dbname && reuse_previous)
{
- initPQExpBuffer(&connstr);
- appendPQExpBufferStr(&connstr, "dbname=");
- appendConnStrVal(&connstr, PQdb(o_conn));
- dbname = connstr.data;
- /* has_connection_string=true would be a dead store */
+ /* We failed to create the cinfo structure */
+ pg_log_error("out of memory");
+ success = false;
}
- else
- connstr.data = NULL;
/*
* If the user asked to be prompted for a password, ask for one now. If
* the postmaster's log. But libpq offers no API that would let us obtain
* a password and then continue with the first connection attempt.
*/
- if (pset.getPassword == TRI_YES)
+ if (pset.getPassword == TRI_YES && success)
{
/*
- * If a connstring or URI is provided, we can't be sure we know which
- * username will be used, since we haven't parsed that argument yet.
+ * If a connstring or URI is provided, we don't know which username
+ * will be used, since we haven't dug that out of the connstring.
* Don't risk issuing a misleading prompt. As in startup.c, it does
- * not seem worth working harder, since this getPassword option is
+ * not seem worth working harder, since this getPassword setting is
* normally only used in noninteractive cases.
*/
password = prompt_for_password(has_connection_string ? NULL : user);
password = NULL;
}
- while (true)
+ /* Loop till we have a connection or fail, which we might've already */
+ while (success)
{
-#define PARAMS_ARRAY_SIZE 9
- const char **keywords = pg_malloc(PARAMS_ARRAY_SIZE * sizeof(*keywords));
- const char **values = pg_malloc(PARAMS_ARRAY_SIZE * sizeof(*values));
- int paramnum = -1;
-
- keywords[++paramnum] = "host";
- values[paramnum] = host;
- if (hostaddr && *hostaddr)
- {
- keywords[++paramnum] = "hostaddr";
- values[paramnum] = hostaddr;
- }
- keywords[++paramnum] = "port";
- values[paramnum] = port;
- keywords[++paramnum] = "user";
- values[paramnum] = user;
+ const char **keywords = pg_malloc((nconnopts + 1) * sizeof(*keywords));
+ const char **values = pg_malloc((nconnopts + 1) * sizeof(*values));
+ int paramnum = 0;
+ PQconninfoOption *ci;
/*
- * Position in the array matters when the dbname is a connection
- * string, because settings in a connection string override earlier
- * array entries only. Thus, user= in the connection string always
- * takes effect, but client_encoding= often will not.
+ * Copy non-default settings into the PQconnectdbParams parameter
+ * arrays; but override any values specified old-style, as well as the
+ * password and a couple of fields we want to set forcibly.
*
- * If you change this code, also change the initial-connection code in
+ * If you change this code, see also the initial-connection code in
* main(). For no good reason, a connection string password= takes
* precedence in main() but not here.
*/
- keywords[++paramnum] = "dbname";
- values[paramnum] = dbname;
- keywords[++paramnum] = "password";
- values[paramnum] = password;
- keywords[++paramnum] = "fallback_application_name";
- values[paramnum] = pset.progname;
- keywords[++paramnum] = "client_encoding";
- values[paramnum] = (pset.notty || getenv("PGCLIENTENCODING")) ? NULL : "auto";
-
+ for (ci = cinfo; ci->keyword; ci++)
+ {
+ keywords[paramnum] = ci->keyword;
+
+ if (dbname && strcmp(ci->keyword, "dbname") == 0)
+ values[paramnum++] = dbname;
+ else if (user && strcmp(ci->keyword, "user") == 0)
+ values[paramnum++] = user;
+ else if (host && strcmp(ci->keyword, "host") == 0)
+ values[paramnum++] = host;
+ else if (host && !same_host && strcmp(ci->keyword, "hostaddr") == 0)
+ {
+ /* If we're changing the host value, drop any old hostaddr */
+ values[paramnum++] = NULL;
+ }
+ else if (port && strcmp(ci->keyword, "port") == 0)
+ values[paramnum++] = port;
+ else if (strcmp(ci->keyword, "password") == 0)
+ values[paramnum++] = password;
+ else if (strcmp(ci->keyword, "fallback_application_name") == 0)
+ values[paramnum++] = pset.progname;
+ else if (strcmp(ci->keyword, "client_encoding") == 0)
+ values[paramnum++] = (pset.notty || getenv("PGCLIENTENCODING")) ? NULL : "auto";
+ else if (ci->val)
+ values[paramnum++] = ci->val;
+ /* else, don't bother making libpq parse this keyword */
+ }
/* add array terminator */
- keywords[++paramnum] = NULL;
+ keywords[paramnum] = NULL;
values[paramnum] = NULL;
- n_conn = PQconnectdbParams(keywords, values, true);
+ /* Note we do not want libpq to re-expand the dbname parameter */
+ n_conn = PQconnectdbParams(keywords, values, false);
pg_free(keywords);
pg_free(values);
- /* We can immediately discard the password -- no longer needed */
- if (password)
- pg_free(password);
-
if (PQstatus(n_conn) == CONNECTION_OK)
break;
*/
password = prompt_for_password(PQuser(n_conn));
PQfinish(n_conn);
+ n_conn = NULL;
continue;
}
+ /*
+ * We'll report the error below ... unless n_conn is NULL, indicating
+ * that libpq didn't have enough memory to make a PGconn.
+ */
+ if (n_conn == NULL)
+ pg_log_error("out of memory");
+
+ success = false;
+ } /* end retry loop */
+
+ /* Release locally allocated data, whether we succeeded or not */
+ if (password)
+ pg_free(password);
+ if (cinfo)
+ PQconninfoFree(cinfo);
+
+ if (!success)
+ {
/*
* Failed to connect to the database. In interactive mode, keep the
* previous connection to the DB; in scripting mode, close our
*/
if (pset.cur_cmd_interactive)
{
- pg_log_info("%s", PQerrorMessage(n_conn));
+ if (n_conn)
+ {
+ pg_log_info("%s", PQerrorMessage(n_conn));
+ PQfinish(n_conn);
+ }
/* pset.db is left unmodified */
if (o_conn)
}
else
{
- pg_log_error("\\connect: %s", PQerrorMessage(n_conn));
+ if (n_conn)
+ {
+ pg_log_error("\\connect: %s", PQerrorMessage(n_conn));
+ PQfinish(n_conn);
+ }
+
if (o_conn)
{
/*
}
}
- PQfinish(n_conn);
- if (connstr.data)
- termPQExpBuffer(&connstr);
return false;
}
- if (connstr.data)
- termPQExpBuffer(&connstr);
/*
* Replace the old connection with the new one, and update