aboutsummaryrefslogtreecommitdiff
path: root/src/bin/psql/common.c
diff options
context:
space:
mode:
authorTom Lane <tgl@sss.pgh.pa.us>2017-04-01 21:44:54 -0400
committerTom Lane <tgl@sss.pgh.pa.us>2017-04-01 21:44:54 -0400
commitf833c847b8fa4782efab45c8371d3cee64292d9b (patch)
tree85f93d92d7f7ab45bab157bb24ff482c6f47b97b /src/bin/psql/common.c
parent41bd155dd656e7f17c02855be7aff234843347cd (diff)
downloadpostgresql-f833c847b8fa4782efab45c8371d3cee64292d9b.tar.gz
postgresql-f833c847b8fa4782efab45c8371d3cee64292d9b.zip
Allow psql variable substitution to occur in backtick command strings.
Previously, text between backquotes in a psql metacommand's arguments was always passed to the shell literally. That considerably hobbles the usefulness of the feature for scripting, so we'd foreseen for a long time that we'd someday want to allow substitution of psql variables into the shell command. IMO the addition of \if metacommands has brought us to that point, since \if can greatly benefit from some sort of client-side expression evaluation capability, and psql itself is not going to grow any such thing in time for v10. Hence, this patch. It allows :VARIABLE to be replaced by the exact contents of the named variable, while :'VARIABLE' is replaced by the variable's contents suitably quoted to become a single shell-command argument. (The quoting rules for that are different from those for SQL literals, so this is a bit of an abuse of the :'VARIABLE' notation, but I doubt anyone will be confused.) As with other situations in psql, no substitution occurs if the word following a colon is not a known variable name. That limits the risk of compatibility problems for existing psql scripts; but the risk isn't zero, so this needs to be called out in the v10 release notes. Discussion: https://postgr.es/m/9561.1490895211@sss.pgh.pa.us
Diffstat (limited to 'src/bin/psql/common.c')
-rw-r--r--src/bin/psql/common.c98
1 files changed, 66 insertions, 32 deletions
diff --git a/src/bin/psql/common.c b/src/bin/psql/common.c
index b06ae9779d5..a2f1259c1e2 100644
--- a/src/bin/psql/common.c
+++ b/src/bin/psql/common.c
@@ -116,19 +116,19 @@ setQFout(const char *fname)
* If the specified variable exists, return its value as a string (malloc'd
* and expected to be freed by the caller); else return NULL.
*
- * If "escape" is true, return the value suitably quoted and escaped,
- * as an identifier or string literal depending on "as_ident".
- * (Failure in escaping should lead to returning NULL.)
+ * If "quote" isn't PQUOTE_PLAIN, then return the value suitably quoted and
+ * escaped for the specified quoting requirement. (Failure in escaping
+ * should lead to printing an error and returning NULL.)
*
* "passthrough" is the pointer previously given to psql_scan_set_passthrough.
* In psql, passthrough points to a ConditionalStack, which we check to
* determine whether variable expansion is allowed.
*/
char *
-psql_get_variable(const char *varname, bool escape, bool as_ident,
+psql_get_variable(const char *varname, PsqlScanQuoteType quote,
void *passthrough)
{
- char *result;
+ char *result = NULL;
const char *value;
/* In an inactive \if branch, suppress all variable substitutions */
@@ -139,40 +139,74 @@ psql_get_variable(const char *varname, bool escape, bool as_ident,
if (!value)
return NULL;
- if (escape)
+ switch (quote)
{
- char *escaped_value;
+ case PQUOTE_PLAIN:
+ result = pg_strdup(value);
+ break;
+ case PQUOTE_SQL_LITERAL:
+ case PQUOTE_SQL_IDENT:
+ {
+ /*
+ * For these cases, we use libpq's quoting functions, which
+ * assume the string is in the connection's client encoding.
+ */
+ char *escaped_value;
- if (!pset.db)
- {
- psql_error("cannot escape without active connection\n");
- return NULL;
- }
+ if (!pset.db)
+ {
+ psql_error("cannot escape without active connection\n");
+ return NULL;
+ }
- if (as_ident)
- escaped_value =
- PQescapeIdentifier(pset.db, value, strlen(value));
- else
- escaped_value =
- PQescapeLiteral(pset.db, value, strlen(value));
+ if (quote == PQUOTE_SQL_LITERAL)
+ escaped_value =
+ PQescapeLiteral(pset.db, value, strlen(value));
+ else
+ escaped_value =
+ PQescapeIdentifier(pset.db, value, strlen(value));
- if (escaped_value == NULL)
- {
- const char *error = PQerrorMessage(pset.db);
+ if (escaped_value == NULL)
+ {
+ const char *error = PQerrorMessage(pset.db);
- psql_error("%s", error);
- return NULL;
- }
+ psql_error("%s", error);
+ return NULL;
+ }
- /*
- * Rather than complicate the lexer's API with a notion of which
- * free() routine to use, just pay the price of an extra strdup().
- */
- result = pg_strdup(escaped_value);
- PQfreemem(escaped_value);
+ /*
+ * Rather than complicate the lexer's API with a notion of
+ * which free() routine to use, just pay the price of an extra
+ * strdup().
+ */
+ result = pg_strdup(escaped_value);
+ PQfreemem(escaped_value);
+ break;
+ }
+ case PQUOTE_SHELL_ARG:
+ {
+ /*
+ * For this we use appendShellStringNoError, which is
+ * encoding-agnostic, which is fine since the shell probably
+ * is too. In any case, the only special character is "'",
+ * which is not known to appear in valid multibyte characters.
+ */
+ PQExpBufferData buf;
+
+ initPQExpBuffer(&buf);
+ if (!appendShellStringNoError(&buf, value))
+ {
+ psql_error("shell command argument contains a newline or carriage return: \"%s\"\n",
+ value);
+ free(buf.data);
+ return NULL;
+ }
+ result = buf.data;
+ break;
+ }
+
+ /* No default: we want a compiler warning for missing cases */
}
- else
- result = pg_strdup(value);
return result;
}