diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 7e30c57c35b..b20d64cc05a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -156,8 +156,8 @@ PostgreSQL documentation
Use the file filename
as the source of commands instead of reading commands interactively.
After the file is processed, psql
- terminates. This is in many ways equivalent to the internal
- command \i.
+ terminates. This is in many ways equivalent to the meta-command
+ \i.
@@ -223,7 +223,7 @@ PostgreSQL documentation
List all available databases, then exit. Other non-connection
- options are ignored. This is similar to the internal command
+ options are ignored. This is similar to the meta-command
\list.
@@ -393,9 +393,9 @@ PostgreSQL documentation
Perform a variable assignment, like the \set
- internal command. Note that you must separate name and value, if
+ meta-command. Note that you must separate name and value, if
any, by an equal sign on the command line. To unset a variable,
- leave off the equal sign. To just set a variable without a value,
+ leave off the equal sign. To set a variable with an empty value,
use the equal sign but leave off the value. These assignments are
done during a very early stage of start-up, so variables reserved
for internal purposes might get overwritten later.
@@ -659,32 +659,32 @@ testdb=>
- To include whitespace into an argument you can quote it with a
- single quote. To include a single quote into such an argument,
- use two single quotes. Anything contained in single quotes is
+ To include whitespace in an argument you can quote it with
+ single quotes. To include a single quote in an argument,
+ write two single quotes within single-quoted text.
+ Anything contained in single quotes is
furthermore subject to C-like substitutions for
\n (new line), \t (tab),
+ \b (backspace), \r (carriage return),
+ \f (form feed),
\digits (octal), and
\xdigits (hexadecimal).
+ A backslash preceding any other character within single-quoted text
+ quotes that single character, whatever it is.
- If an unquoted argument begins with a colon (:),
- it is taken as a psql> variable and the value of the
- variable is used as the argument instead. If the variable name is
- surrounded by single quotes (e.g. :'var'), it
- will be escaped as an SQL literal and the result will be used as
- the argument. If the variable name is surrounded by double quotes,
- it will be escaped as an SQL identifier and the result will be used
- as the argument.
+ Within an argument, text that is enclosed in backquotes
+ (`) is taken as a command line that is passed to the
+ shell. The output of the command (with any trailing newline removed)
+ replaces the backquoted text.
- Arguments that are enclosed in backquotes (`)
- are taken as a command line that is passed to the shell. The
- output of the command (with any trailing newline removed) is taken
- as the argument value. The above escape sequences also apply in
- backquotes.
+ If an unquoted colon (:) followed by a
+ psql> variable name appears within an argument, it is
+ replaced by the variable's value, as described in .
@@ -1803,15 +1803,16 @@ lo_import 152801
\prompt [ text ] name
- Prompts the user to set variable name>. An optional prompt, name>.
+ An optional prompt string, text>, can be specified. (For multiword
- prompts, use single quotes.)
+ prompts, surround the text with single quotes.)
By default, \prompt> uses the terminal for input and
- output. However, if the
@@ -2197,14 +2198,19 @@ lo_import 152801
- Sets the internal variable psql> variable name to value or, if more than one value
- is given, to the concatenation of all of them. If no second
- argument is given, the variable is just set with no value. To
+ class="parameter">value, or if more than one value
+ is given, to the concatenation of all of them. If only one
+ argument is given, the variable is set with an empty value. To
unset a variable, use the \unset command.
+
+ \set> without any arguments displays the names and values
+ of all currently-set psql> variables.
+
+
Valid variable names can contain letters, digits, and
underscores. See the section
- This command is totally separate from the SQL
+ This command is unrelated to the SQL
command .
@@ -2293,6 +2299,18 @@ lo_import 152801
+
+ \unset name
+
+
+
+ Unsets (deletes) the psql> variable name.
+
+
+
+
+
\wfilename\w|>command
@@ -2467,18 +2485,28 @@ lo_import 152801
To set a variable, use the psql meta-command
- \set:
+ \set. For example,
testdb=> \set foo bar
sets the variable foo to the value
bar. To retrieve the content of the variable, precede
- the name with a colon and use it as the argument of any slash
- command:
+ the name with a colon, for example:
testdb=> \echo :foo
bar
-
+
+ This works in both regular SQL commands and meta-commands; there is
+ more detail in , below.
+
+
+
+ If you call \set without a second argument, the
+ variable is set, with an empty string as value. To unset (i.e., delete)
+ a variable, use the command \unset. To show the
+ values of all variables, call \set without any argument.
+
@@ -2495,12 +2523,6 @@ bar
-
- If you call \set without a second argument, the
- variable is set, with an empty string as value. To unset (or delete) a
- variable, use the command \unset.
-
-
A number of these variables are treated specially
by psql. They represent certain option
@@ -2863,47 +2885,57 @@ bar
-
- SQL Interpolation
+
+ SQL Interpolation
- An additional useful feature of psql
+ A key feature of psql
variables is that you can substitute (interpolate)
- them into regular SQL statements.
- psql provides special facilities for
- ensuring that values used as SQL literals and identifiers are
- properly escaped. The syntax for interpolating a value without
- any special escaping is again to prepend the variable name with a colon
- (:):
+ them into regular SQL statements, as well as the
+ arguments of meta-commands. Furthermore,
+ psql provides facilities for
+ ensuring that variable values used as SQL literals and identifiers are
+ properly quoted. The syntax for interpolating a value without
+ any quoting is to prepend the variable name with a colon
+ (:). For example,
testdb=> \set foo 'my_table'
testdb=> SELECT * FROM :foo;
- would then query the table my_table. Note that this
+ would query the table my_table. Note that this
may be unsafe: the value of the variable is copied literally, so it can
- even contain unbalanced quotes or backslash commands. You must make sure
+ contain unbalanced quotes, or even backslash commands. You must make sure
that it makes sense where you put it.
When a value is to be used as an SQL literal or identifier, it is
- safest to arrange for it to be escaped. To escape the value of
+ safest to arrange for it to be quoted. To quote the value of
a variable as an SQL literal, write a colon followed by the variable
- name in single quotes. To escape the value an SQL identifier, write
- a colon followed by the variable name in double quotes. The previous
- example would be more safely written this way:
+ name in single quotes. To quote the value as an SQL identifier, write
+ a colon followed by the variable name in double quotes.
+ These constructs deal correctly with quotes and other special
+ characters embedded within the variable value.
+ The previous example would be more safely written this way:
testdb=> \set foo 'my_table'
testdb=> SELECT * FROM :"foo";
- Variable interpolation will not be performed into quoted
- SQL entities.
- One possible use of this mechanism is to
- copy the contents of a file into a table column. First load the file into a
- variable and then proceed as above:
+ Variable interpolation will not be performed within quoted
+ SQL literals and identifiers. Therefore, a
+ construction such as ':foo'> doesn't work to produce a quoted
+ literal from a variable's value (and it would be unsafe if it did work,
+ since it wouldn't correctly handle quotes embedded in the value).
+
+
+
+ One example use of this mechanism is to
+ copy the contents of a file into a table column.
+ First load the file into a variable and then interpolate the variable's
+ value as a quoted string:
testdb=> \set content `cat my_file.txt`
testdb=> INSERT INTO my_table VALUES (:'content');
@@ -2914,17 +2946,20 @@ testdb=> INSERT INTO my_table VALUES (:'content');
Since colons can legally appear in SQL commands, an apparent attempt
- at interpolation (such as :name,
+ at interpolation (that is, :name,
:'name', or :"name") is not
- changed unless the named variable is currently set. In any case, you
+ replaced unless the named variable is currently set. In any case, you
can escape a colon with a backslash to protect it from substitution.
- (The colon syntax for variables is standard SQL for
+
+
+
+ The colon syntax for variables is standard SQL for
embedded query languages, such as ECPG.
- The colon syntax for array slices and type casts are
- PostgreSQL extensions, hence the
- conflict. The colon syntax for escaping a variable's value as an
- SQL literal or identifier is a psql
- extension.)
+ The colon syntaxes for array slices and type casts are
+ PostgreSQL extensions, which can sometimes
+ conflict with the standard usage. The colon-quote syntax for escaping a
+ variable's value as an SQL literal or identifier is a
+ psql extension.
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index 6d9cd6492f6..2c389021be7 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -121,7 +121,7 @@ HandleSlashCmds(PsqlScanState scan_state,
/* eat any remaining arguments after a valid command */
/* note we suppress evaluation of backticks here */
while ((arg = psql_scan_slash_option(scan_state,
- OT_VERBATIM, NULL, false)))
+ OT_NO_EVAL, NULL, false)))
{
psql_error("\\%s: extra argument \"%s\" ignored\n", cmd, arg);
free(arg);
diff --git a/src/bin/psql/psqlscan.h b/src/bin/psql/psqlscan.h
index b2545d0ebf2..6264def953d 100644
--- a/src/bin/psql/psqlscan.h
+++ b/src/bin/psql/psqlscan.h
@@ -33,7 +33,7 @@ enum slash_option_type
OT_SQLIDHACK, /* SQL identifier, but don't downcase */
OT_FILEPIPE, /* it's a filename or pipe */
OT_WHOLE_LINE, /* just snarf the rest of the line */
- OT_VERBATIM /* literal (no backticks or variables) */
+ OT_NO_EVAL /* no expansion of backticks or variables */
};
diff --git a/src/bin/psql/psqlscan.l b/src/bin/psql/psqlscan.l
index 1df8f3aa4f6..d4a9d94a437 100644
--- a/src/bin/psql/psqlscan.l
+++ b/src/bin/psql/psqlscan.l
@@ -103,6 +103,8 @@ static PQExpBuffer output_buf; /* current output buffer */
/* these variables do not need to be saved across calls */
static enum slash_option_type option_type;
static char *option_quote;
+static int unquoted_option_chars;
+static int backtick_start_offset;
/* Return values from yylex() */
@@ -114,6 +116,7 @@ static char *option_quote;
int yylex(void);
+static void evaluate_backtick(void);
static void push_new_buffer(const char *newstr, const char *varname);
static void pop_buffer_stack(PsqlScanState state);
static bool var_is_current_source(PsqlScanState state, const char *varname);
@@ -182,11 +185,11 @@ static void escape_variable(bool as_ident);
%x xus
/* Additional exclusive states for psql only: lex backslash commands */
%x xslashcmd
+%x xslashargstart
%x xslasharg
%x xslashquote
%x xslashbackquote
-%x xslashdefaultarg
-%x xslashquotedarg
+%x xslashdquote
%x xslashwholeline
%x xslashend
@@ -900,17 +903,53 @@ other .
}
-{
- /* eat any whitespace, then decide what to do at first nonblank */
+{
+ /*
+ * Discard any whitespace before argument, then go to xslasharg state.
+ * An exception is that "|" is only special at start of argument, so we
+ * check for it here.
+ */
{space}+ { }
-"\\" {
+"|" {
+ if (option_type == OT_FILEPIPE)
+ {
+ /* treat like whole-string case */
+ ECHO;
+ BEGIN(xslashwholeline);
+ }
+ else
+ {
+ /* vertical bar is not special otherwise */
+ yyless(0);
+ BEGIN(xslasharg);
+ }
+ }
+
+{other} {
+ yyless(0);
+ BEGIN(xslasharg);
+ }
+
+}
+
+{
+ /*
+ * Default processing of text in a slash command's argument.
+ *
+ * Note: unquoted_option_chars counts the number of characters at the
+ * end of the argument that were not subject to any form of quoting.
+ * psql_scan_slash_option needs this to strip trailing semicolons safely.
+ */
+
+{space}|"\\" {
/*
+ * Unquoted space is end of arg; do not eat. Likewise
* backslash is end of command or next command, do not eat
*
* XXX this means we can't conveniently accept options
- * that start with a backslash; therefore, option
+ * that include unquoted backslashes; therefore, option
* processing that encourages use of backslashes is rather
* broken.
*/
@@ -920,26 +959,27 @@ other .
{quote} {
*option_quote = '\'';
+ unquoted_option_chars = 0;
BEGIN(xslashquote);
}
"`" {
- if (option_type == OT_VERBATIM)
- {
- /* in verbatim mode, backquote is not special */
- ECHO;
- BEGIN(xslashdefaultarg);
- }
- else
- {
- *option_quote = '`';
- BEGIN(xslashbackquote);
- }
+ backtick_start_offset = output_buf->len;
+ *option_quote = '`';
+ unquoted_option_chars = 0;
+ BEGIN(xslashbackquote);
+ }
+
+{dquote} {
+ ECHO;
+ *option_quote = '"';
+ unquoted_option_chars = 0;
+ BEGIN(xslashdquote);
}
:{variable_char}+ {
/* Possible psql variable substitution */
- if (option_type == OT_VERBATIM)
+ if (option_type == OT_NO_EVAL)
ECHO;
else
{
@@ -959,71 +999,54 @@ other .
*/
if (value)
appendPQExpBufferStr(output_buf, value);
+ else
+ ECHO;
+
+ *option_quote = ':';
}
-
- *option_quote = ':';
-
- return LEXRES_OK;
+ unquoted_option_chars = 0;
}
:'{variable_char}+' {
- if (option_type == OT_VERBATIM)
+ if (option_type == OT_NO_EVAL)
ECHO;
else
{
escape_variable(false);
- return LEXRES_OK;
+ *option_quote = ':';
}
+ unquoted_option_chars = 0;
}
:\"{variable_char}+\" {
- if (option_type == OT_VERBATIM)
+ if (option_type == OT_NO_EVAL)
ECHO;
else
{
escape_variable(true);
- return LEXRES_OK;
+ *option_quote = ':';
}
+ unquoted_option_chars = 0;
}
:'{variable_char}* {
/* Throw back everything but the colon */
yyless(1);
+ unquoted_option_chars++;
ECHO;
- BEGIN(xslashdefaultarg);
}
:\"{variable_char}* {
/* Throw back everything but the colon */
yyless(1);
+ unquoted_option_chars++;
ECHO;
- BEGIN(xslashdefaultarg);
- }
-
-"|" {
- ECHO;
- if (option_type == OT_FILEPIPE)
- {
- /* treat like whole-string case */
- BEGIN(xslashwholeline);
- }
- else
- {
- /* treat like default case */
- BEGIN(xslashdefaultarg);
- }
- }
-
-{dquote} {
- *option_quote = '"';
- ECHO;
- BEGIN(xslashquotedarg);
}
{other} {
+ unquoted_option_chars++;
ECHO;
- BEGIN(xslashdefaultarg);
}
}
@@ -1034,7 +1057,7 @@ other .
* sequences
*/
-{quote} { return LEXRES_OK; }
+{quote} { BEGIN(xslasharg); }
{xqdouble} { appendPQExpBufferChar(output_buf, '\''); }
@@ -1064,55 +1087,28 @@ other .
{
/*
- * backticked text: copy everything until next backquote or end of line.
- * Invocation of the command will happen in psql_scan_slash_option.
+ * backticked text: copy everything until next backquote, then evaluate.
+ *
+ * XXX Possible future behavioral change: substitute for :VARIABLE?
*/
-"`" { return LEXRES_OK; }
+"`" {
+ /* In NO_EVAL mode, don't evaluate the command */
+ if (option_type != OT_NO_EVAL)
+ evaluate_backtick();
+ BEGIN(xslasharg);
+ }
{other}|\n { ECHO; }
}
-{
- /*
- * Copy everything until unquoted whitespace or end of line. Quotes
- * do not get stripped yet.
- */
-
-{space} {
- yyless(0);
- return LEXRES_OK;
- }
-
-"\\" {
- /*
- * unquoted backslash is end of command or next command,
- * do not eat
- *
- * (this was not the behavior pre-8.0, but it seems
- * consistent)
- */
- yyless(0);
- return LEXRES_OK;
- }
-
-{dquote} {
- *option_quote = '"';
- ECHO;
- BEGIN(xslashquotedarg);
- }
-
-{other} { ECHO; }
-
-}
-
-{
- /* double-quoted text within a default-type argument: copy */
+{
+ /* double-quoted text: copy verbatim, including the double quotes */
{dquote} {
ECHO;
- BEGIN(xslashdefaultarg);
+ BEGIN(xslasharg);
}
{other}|\n { ECHO; }
@@ -1461,7 +1457,7 @@ psql_scan_slash_command(PsqlScanState state)
* letters.
*
* if quote is not NULL, *quote is set to 0 if no quoting was found, else
- * the quote symbol.
+ * the last quote symbol used in the argument.
*
* if semicolon is true, unquoted trailing semicolon(s) that would otherwise
* be taken as part of the option string will be stripped.
@@ -1480,7 +1476,6 @@ psql_scan_slash_option(PsqlScanState state,
PQExpBufferData mybuf;
int lexresult;
char local_quote;
- bool badarg;
/* Must be scanning already */
psql_assert(state->scanbufhandle);
@@ -1497,6 +1492,7 @@ psql_scan_slash_option(PsqlScanState state,
output_buf = &mybuf;
option_type = type;
option_quote = quote;
+ unquoted_option_chars = 0;
if (state->buffer_stack != NULL)
yy_switch_to_buffer(state->buffer_stack->buf);
@@ -1506,7 +1502,7 @@ psql_scan_slash_option(PsqlScanState state,
if (type == OT_WHOLE_LINE)
BEGIN(xslashwholeline);
else
- BEGIN(xslasharg);
+ BEGIN(xslashargstart);
/* And lex. */
lexresult = yylex();
@@ -1517,85 +1513,18 @@ psql_scan_slash_option(PsqlScanState state,
* a quoted string, as indicated by YY_START, EOL is an error.
*/
psql_assert(lexresult == LEXRES_EOL || lexresult == LEXRES_OK);
- badarg = false;
+
switch (YY_START)
{
+ case xslashargstart:
+ /* empty arg */
+ break;
case xslasharg:
- /* empty arg, or possibly a psql variable substitution */
- break;
- case xslashquote:
- if (lexresult != LEXRES_OK)
- badarg = true; /* hit EOL not ending quote */
- break;
- case xslashbackquote:
- if (lexresult != LEXRES_OK)
- badarg = true; /* hit EOL not ending quote */
- else
- {
- /* Perform evaluation of backticked command */
- char *cmd = mybuf.data;
- FILE *fd;
- bool error = false;
- PQExpBufferData output;
- char buf[512];
- size_t result;
-
- fd = popen(cmd, PG_BINARY_R);
- if (!fd)
- {
- psql_error("%s: %s\n", cmd, strerror(errno));
- error = true;
- }
-
- initPQExpBuffer(&output);
-
- if (!error)
- {
- do
- {
- result = fread(buf, 1, sizeof(buf), fd);
- if (ferror(fd))
- {
- psql_error("%s: %s\n", cmd, strerror(errno));
- error = true;
- break;
- }
- appendBinaryPQExpBuffer(&output, buf, result);
- } while (!feof(fd));
- }
-
- if (fd && pclose(fd) == -1)
- {
- psql_error("%s: %s\n", cmd, strerror(errno));
- error = true;
- }
-
- if (PQExpBufferBroken(&output))
- {
- psql_error("%s: out of memory\n", cmd);
- error = true;
- }
-
- /* Now done with cmd, transfer result to mybuf */
- resetPQExpBuffer(&mybuf);
-
- if (!error)
- {
- /* strip any trailing newline */
- if (output.len > 0 &&
- output.data[output.len - 1] == '\n')
- output.len--;
- appendBinaryPQExpBuffer(&mybuf, output.data, output.len);
- }
-
- termPQExpBuffer(&output);
- }
- break;
- case xslashdefaultarg:
- /* Strip any trailing semi-colons if requested */
+ /* Strip any unquoted trailing semi-colons if requested */
if (semicolon)
{
- while (mybuf.len > 0 &&
+ while (unquoted_option_chars-- > 0 &&
+ mybuf.len > 0 &&
mybuf.data[mybuf.len - 1] == ';')
{
mybuf.data[--mybuf.len] = '\0';
@@ -1642,10 +1571,13 @@ psql_scan_slash_option(PsqlScanState state,
}
}
break;
- case xslashquotedarg:
- /* must have hit EOL inside double quotes */
- badarg = true;
- break;
+ case xslashquote:
+ case xslashbackquote:
+ case xslashdquote:
+ /* must have hit EOL inside quotes */
+ psql_error("unterminated quoted string\n");
+ termPQExpBuffer(&mybuf);
+ return NULL;
case xslashwholeline:
/* always okay */
break;
@@ -1655,13 +1587,6 @@ psql_scan_slash_option(PsqlScanState state,
exit(1);
}
- if (badarg)
- {
- psql_error("unterminated quoted string\n");
- termPQExpBuffer(&mybuf);
- return NULL;
- }
-
/*
* An unquoted empty argument isn't possible unless we are at end of
* command. Return NULL instead.
@@ -1702,6 +1627,74 @@ psql_scan_slash_command_end(PsqlScanState state)
/* There are no possible errors in this lex state... */
}
+/*
+ * Evaluate a backticked substring of a slash command's argument.
+ *
+ * The portion of output_buf starting at backtick_start_offset is evaluated
+ * as a shell command and then replaced by the command's output.
+ */
+static void
+evaluate_backtick(void)
+{
+ char *cmd = output_buf->data + backtick_start_offset;
+ PQExpBufferData cmd_output;
+ FILE *fd;
+ bool error = false;
+ char buf[512];
+ size_t result;
+
+ initPQExpBuffer(&cmd_output);
+
+ fd = popen(cmd, PG_BINARY_R);
+ if (!fd)
+ {
+ psql_error("%s: %s\n", cmd, strerror(errno));
+ error = true;
+ }
+
+ if (!error)
+ {
+ do
+ {
+ result = fread(buf, 1, sizeof(buf), fd);
+ if (ferror(fd))
+ {
+ psql_error("%s: %s\n", cmd, strerror(errno));
+ error = true;
+ break;
+ }
+ appendBinaryPQExpBuffer(&cmd_output, buf, result);
+ } while (!feof(fd));
+ }
+
+ if (fd && pclose(fd) == -1)
+ {
+ psql_error("%s: %s\n", cmd, strerror(errno));
+ error = true;
+ }
+
+ if (PQExpBufferBroken(&cmd_output))
+ {
+ psql_error("%s: out of memory\n", cmd);
+ error = true;
+ }
+
+ /* Now done with cmd, delete it from output_buf */
+ output_buf->len = backtick_start_offset;
+ output_buf->data[output_buf->len] = '\0';
+
+ /* If no error, transfer result to output_buf */
+ if (!error)
+ {
+ /* strip any trailing newline */
+ if (cmd_output.len > 0 &&
+ cmd_output.data[cmd_output.len - 1] == '\n')
+ cmd_output.len--;
+ appendBinaryPQExpBuffer(output_buf, cmd_output.data, cmd_output.len);
+ }
+
+ termPQExpBuffer(&cmd_output);
+}
/*
* Push the given string onto the stack of stuff to scan.