Commit e984ef58 authored by Tom Lane's avatar Tom Lane

Support \if ... \elif ... \else ... \endif in psql scripting.

This patch adds nestable conditional blocks to psql.  The control
structure feature per se is complete, but the boolean expressions
understood by \if and \elif are pretty primitive; basically, after
variable substitution and backtick expansion, the result has to be
"true" or "false" or one of the other standard spellings of a boolean
value.  But that's enough for many purposes, since you can always
do the heavy lifting on the server side; and we can extend it later.

Along the way, pay down some of the technical debt that had built up
around psql/command.c:
* Refactor exec_command() into a function per command, instead of
being a 1500-line monstrosity.  This makes the file noticeably longer
because of repetitive function header/trailer overhead, but it seems
much more readable.
* Teach psql_get_variable() and psqlscanslash.l to suppress variable
substitution and backtick expansion on the basis of the conditional
stack state, thereby allowing removal of the OT_NO_EVAL kluge.
* Fix the no-doubt-once-expedient hack of sometimes silently substituting
mainloop.c's previous_buf for query_buf when calling HandleSlashCmds.
(It's a bit remarkable that commands like \r worked at all with that.)
Recall of a previous query is now done explicitly in the slash commands
where that should happen.

Corey Huinker, reviewed by Fabien Coelho, further hacking by me

Discussion: https://postgr.es/m/CADkLM=c94OSRTnat=LX0ivNq4pxDNeoomFfYvBKM5N_xfmLtAA@mail.gmail.com
parent ffae6733
......@@ -2063,6 +2063,95 @@ hello 10
</varlistentry>
<varlistentry>
<term><literal>\if</literal> <replaceable class="parameter">expression</replaceable></term>
<term><literal>\elif</literal> <replaceable class="parameter">expression</replaceable></term>
<term><literal>\else</literal></term>
<term><literal>\endif</literal></term>
<listitem>
<para>
This group of commands implements nestable conditional blocks.
A conditional block must begin with an <command>\if</command> and end
with an <command>\endif</command>. In between there may be any number
of <command>\elif</command> clauses, which may optionally be followed
by a single <command>\else</command> clause. Ordinary queries and
other types of backslash commands may (and usually do) appear between
the commands forming a conditional block.
</para>
<para>
The <command>\if</command> and <command>\elif</command> commands read
their argument(s) and evaluate them as a boolean expression. If the
expression yields <literal>true</literal> then processing continues
normally; otherwise, lines are skipped until a
matching <command>\elif</command>, <command>\else</command>,
or <command>\endif</command> is reached. Once
an <command>\if</command> or <command>\elif</command> test has
succeeded, the arguments of later <command>\elif</command> commands in
the same block are not evaluated but are treated as false. Lines
following an <command>\else</command> are processed only if no earlier
matching <command>\if</command> or <command>\elif</command> succeeded.
</para>
<para>
The <replaceable class="parameter">expression</replaceable> argument
of an <command>\if</command> or <command>\elif</command> command
is subject to variable interpolation and backquote expansion, just
like any other backslash command argument. After that it is evaluated
like the value of an on/off option variable. So a valid value
is any unambiguous case-insensitive match for one of:
<literal>true</literal>, <literal>false</literal>, <literal>1</literal>,
<literal>0</literal>, <literal>on</literal>, <literal>off</literal>,
<literal>yes</literal>, <literal>no</literal>. For example,
<literal>t</literal>, <literal>T</literal>, and <literal>tR</literal>
will all be considered to be <literal>true</literal>.
</para>
<para>
Expressions that do not properly evaluate to true or false will
generate a warning and be treated as false.
</para>
<para>
Lines being skipped are parsed normally to identify queries and
backslash commands, but queries are not sent to the server, and
backslash commands other than conditionals
(<command>\if</command>, <command>\elif</command>,
<command>\else</command>, <command>\endif</command>) are
ignored. Conditional commands are checked only for valid nesting.
Variable references in skipped lines are not expanded, and backquote
expansion is not performed either.
</para>
<para>
All the backslash commands of a given conditional block must appear in
the same source file. If EOF is reached on the main input file or an
<command>\include</command>-ed file before all local
<command>\if</command>-blocks have been closed,
then <application>psql</> will raise an error.
</para>
<para>
Here is an example:
</para>
<programlisting>
-- check for the existence of two separate records in the database and store
-- the results in separate psql variables
SELECT
EXISTS(SELECT 1 FROM customer WHERE customer_id = 123) as is_customer,
EXISTS(SELECT 1 FROM employee WHERE employee_id = 456) as is_employee
\gset
\if :is_customer
SELECT * FROM customer WHERE customer_id = 123;
\elif :is_employee
\echo 'is not a customer but is an employee'
SELECT * FROM employee WHERE employee_id = 456;
\else
\if yes
\echo 'not a customer or employee'
\else
\echo 'this will never print'
\endif
\endif
</programlisting>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>\l[+]</literal> or <literal>\list[+] [ <link linkend="APP-PSQL-patterns"><replaceable class="parameter">pattern</replaceable></link> ]</literal></term>
<listitem>
......@@ -3715,7 +3804,8 @@ testdb=&gt; <userinput>INSERT INTO my_table VALUES (:'content');</userinput>
<listitem>
<para>
In prompt 1 normally <literal>=</literal>,
but <literal>^</literal> if in single-line mode,
but <literal>@</literal> if the session is in an inactive branch of a
conditional block, or <literal>^</literal> if in single-line mode,
or <literal>!</literal> if the session is disconnected from the
database (which can happen if <command>\connect</command> fails).
In prompt 2 <literal>%R</literal> is replaced by a character that
......
......@@ -21,10 +21,10 @@ REFDOCDIR= $(top_srcdir)/doc/src/sgml/ref
override CPPFLAGS := -I. -I$(srcdir) -I$(libpq_srcdir) $(CPPFLAGS)
LDFLAGS += -L$(top_builddir)/src/fe_utils -lpgfeutils -lpq
OBJS= command.o common.o help.o input.o stringutils.o mainloop.o copy.o \
startup.o prompt.o variables.o large_obj.o describe.o \
crosstabview.o tab-complete.o \
sql_help.o psqlscanslash.o \
OBJS= command.o common.o conditional.o copy.o crosstabview.o \
describe.o help.o input.o large_obj.o mainloop.o \
prompt.o psqlscanslash.o sql_help.o startup.o stringutils.o \
tab-complete.o variables.o \
$(WIN32RES)
......
......@@ -56,14 +56,103 @@ typedef enum EditableObjectType
EditableView
} EditableObjectType;
/* functions for use in this file */
/* local function declarations */
static backslashResult exec_command(const char *cmd,
PsqlScanState scan_state,
ConditionalStack cstack,
PQExpBuffer query_buf,
PQExpBuffer previous_buf);
static backslashResult exec_command_a(PsqlScanState scan_state, bool active_branch);
static backslashResult exec_command_C(PsqlScanState scan_state, bool active_branch);
static backslashResult exec_command_connect(PsqlScanState scan_state, bool active_branch);
static backslashResult exec_command_cd(PsqlScanState scan_state, bool active_branch,
const char *cmd);
static backslashResult exec_command_conninfo(PsqlScanState scan_state, bool active_branch);
static backslashResult exec_command_copy(PsqlScanState scan_state, bool active_branch);
static backslashResult exec_command_copyright(PsqlScanState scan_state, bool active_branch);
static backslashResult exec_command_crosstabview(PsqlScanState scan_state, bool active_branch);
static backslashResult exec_command_d(PsqlScanState scan_state, bool active_branch,
const char *cmd);
static backslashResult exec_command_edit(PsqlScanState scan_state, bool active_branch,
PQExpBuffer query_buf, PQExpBuffer previous_buf);
static backslashResult exec_command_ef(PsqlScanState scan_state, bool active_branch,
PQExpBuffer query_buf);
static bool do_edit(const char *filename_arg, PQExpBuffer query_buf,
int lineno, bool *edited);
static backslashResult exec_command_ev(PsqlScanState scan_state, bool active_branch,
PQExpBuffer query_buf);
static backslashResult exec_command_echo(PsqlScanState scan_state, bool active_branch,
const char *cmd);
static backslashResult exec_command_elif(PsqlScanState scan_state, ConditionalStack cstack,
PQExpBuffer query_buf);
static backslashResult exec_command_else(PsqlScanState scan_state, ConditionalStack cstack,
PQExpBuffer query_buf);
static backslashResult exec_command_endif(PsqlScanState scan_state, ConditionalStack cstack,
PQExpBuffer query_buf);
static backslashResult exec_command_encoding(PsqlScanState scan_state, bool active_branch);
static backslashResult exec_command_errverbose(PsqlScanState scan_state, bool active_branch);
static backslashResult exec_command_f(PsqlScanState scan_state, bool active_branch);
static backslashResult exec_command_g(PsqlScanState scan_state, bool active_branch,
const char *cmd);
static backslashResult exec_command_gexec(PsqlScanState scan_state, bool active_branch);
static backslashResult exec_command_gset(PsqlScanState scan_state, bool active_branch);
static backslashResult exec_command_help(PsqlScanState scan_state, bool active_branch);
static backslashResult exec_command_html(PsqlScanState scan_state, bool active_branch);
static backslashResult exec_command_include(PsqlScanState scan_state, bool active_branch,
const char *cmd);
static backslashResult exec_command_if(PsqlScanState scan_state, ConditionalStack cstack,
PQExpBuffer query_buf);
static backslashResult exec_command_list(PsqlScanState scan_state, bool active_branch,
const char *cmd);
static backslashResult exec_command_lo(PsqlScanState scan_state, bool active_branch,
const char *cmd);
static backslashResult exec_command_out(PsqlScanState scan_state, bool active_branch);
static backslashResult exec_command_print(PsqlScanState scan_state, bool active_branch,
PQExpBuffer query_buf);
static backslashResult exec_command_password(PsqlScanState scan_state, bool active_branch);
static backslashResult exec_command_prompt(PsqlScanState scan_state, bool active_branch,
const char *cmd);
static backslashResult exec_command_pset(PsqlScanState scan_state, bool active_branch);
static backslashResult exec_command_quit(PsqlScanState scan_state, bool active_branch);
static backslashResult exec_command_reset(PsqlScanState scan_state, bool active_branch,
PQExpBuffer query_buf);
static backslashResult exec_command_s(PsqlScanState scan_state, bool active_branch);
static backslashResult exec_command_set(PsqlScanState scan_state, bool active_branch);
static backslashResult exec_command_setenv(PsqlScanState scan_state, bool active_branch,
const char *cmd);
static backslashResult exec_command_sf(PsqlScanState scan_state, bool active_branch,
const char *cmd);
static backslashResult exec_command_sv(PsqlScanState scan_state, bool active_branch,
const char *cmd);
static backslashResult exec_command_t(PsqlScanState scan_state, bool active_branch);
static backslashResult exec_command_T(PsqlScanState scan_state, bool active_branch);
static backslashResult exec_command_timing(PsqlScanState scan_state, bool active_branch);
static backslashResult exec_command_unset(PsqlScanState scan_state, bool active_branch,
const char *cmd);
static backslashResult exec_command_write(PsqlScanState scan_state, bool active_branch,
const char *cmd,
PQExpBuffer query_buf, PQExpBuffer previous_buf);
static backslashResult exec_command_watch(PsqlScanState scan_state, bool active_branch,
PQExpBuffer query_buf, PQExpBuffer previous_buf);
static backslashResult exec_command_x(PsqlScanState scan_state, bool active_branch);
static backslashResult exec_command_z(PsqlScanState scan_state, bool active_branch);
static backslashResult exec_command_shell_escape(PsqlScanState scan_state, bool active_branch);
static backslashResult exec_command_slash_command_help(PsqlScanState scan_state, bool active_branch);
static char *read_connect_arg(PsqlScanState scan_state);
static PQExpBuffer gather_boolean_expression(PsqlScanState scan_state);
static bool is_true_boolean_expression(PsqlScanState scan_state, const char *name);
static void ignore_boolean_expression(PsqlScanState scan_state);
static void ignore_slash_options(PsqlScanState scan_state);
static void ignore_slash_filepipe(PsqlScanState scan_state);
static void ignore_slash_whole_line(PsqlScanState scan_state);
static bool is_branching_command(const char *cmd);
static void save_query_text_state(PsqlScanState scan_state, ConditionalStack cstack,
PQExpBuffer query_buf);
static void discard_query_text(PsqlScanState scan_state, ConditionalStack cstack,
PQExpBuffer query_buf);
static void copy_previous_query(PQExpBuffer query_buf, PQExpBuffer previous_buf);
static bool do_connect(enum trivalue reuse_previous_specification,
char *dbname, char *user, char *host, char *port);
static bool do_edit(const char *filename_arg, PQExpBuffer query_buf,
int lineno, bool *edited);
static bool do_shell(const char *command);
static bool do_watch(PQExpBuffer query_buf, double sleep);
static bool lookup_object_oid(EditableObjectType obj_type, const char *desc,
......@@ -96,9 +185,18 @@ static void checkWin32Codepage(void);
* just after the '\'. The lexer is advanced past the command and all
* arguments on return.
*
* 'query_buf' contains the query-so-far, which may be modified by
* cstack is the current \if stack state. This will be examined, and
* possibly modified by conditional commands.
*
* query_buf contains the query-so-far, which may be modified by
* execution of the backslash command (for example, \r clears it).
* query_buf can be NULL if there is no query so far.
*
* previous_buf contains the query most recently sent to the server
* (empty if none yet). This should not be modified here, but some
* commands copy its content into query_buf.
*
* query_buf and previous_buf will be NULL when executing a "-c"
* command-line option.
*
* Returns a status code indicating what action is desired, see command.h.
*----------
......@@ -106,19 +204,22 @@ static void checkWin32Codepage(void);
backslashResult
HandleSlashCmds(PsqlScanState scan_state,
PQExpBuffer query_buf)
ConditionalStack cstack,
PQExpBuffer query_buf,
PQExpBuffer previous_buf)
{
backslashResult status = PSQL_CMD_SKIP_LINE;
backslashResult status;
char *cmd;
char *arg;
Assert(scan_state != NULL);
Assert(cstack != NULL);
/* Parse off the command name */
cmd = psql_scan_slash_command(scan_state);
/* And try to execute it */
status = exec_command(cmd, scan_state, query_buf);
status = exec_command(cmd, scan_state, cstack, query_buf, previous_buf);
if (status == PSQL_CMD_UNKNOWN)
{
......@@ -131,14 +232,22 @@ HandleSlashCmds(PsqlScanState scan_state,
if (status != PSQL_CMD_ERROR)
{
/* eat any remaining arguments after a valid command */
/* note we suppress evaluation of backticks here */
/*
* Eat any remaining arguments after a valid command. We want to
* suppress evaluation of backticks in this situation, so transiently
* push an inactive conditional-stack entry.
*/
bool active_branch = conditional_active(cstack);
conditional_stack_push(cstack, IFSTATE_IGNORED);
while ((arg = psql_scan_slash_option(scan_state,
OT_NO_EVAL, NULL, false)))
OT_NORMAL, NULL, false)))
{
if (active_branch)
psql_error("\\%s: extra argument \"%s\" ignored\n", cmd, arg);
free(arg);
}
conditional_stack_pop(cstack);
}
else
{
......@@ -159,56 +268,169 @@ HandleSlashCmds(PsqlScanState scan_state,
return status;
}
/*
* Read and interpret an argument to the \connect slash command.
* Subroutine to actually try to execute a backslash command.
*
* The typical "success" result code is PSQL_CMD_SKIP_LINE, although some
* commands return something else. Failure results are PSQL_CMD_ERROR,
* unless PSQL_CMD_UNKNOWN is more appropriate.
*/
static char *
read_connect_arg(PsqlScanState scan_state)
static backslashResult
exec_command(const char *cmd,
PsqlScanState scan_state,
ConditionalStack cstack,
PQExpBuffer query_buf,
PQExpBuffer previous_buf)
{
char *result;
char quote;
backslashResult status;
bool active_branch = conditional_active(cstack);
/*
* Ideally we should treat the arguments as SQL identifiers. But for
* backwards compatibility with 7.2 and older pg_dump files, we have to
* take unquoted arguments verbatim (don't downcase them). For now,
* double-quoted arguments may be stripped of double quotes (as if SQL
* identifiers). By 7.4 or so, pg_dump files can be expected to
* double-quote all mixed-case \connect arguments, and then we can get rid
* of OT_SQLIDHACK.
* In interactive mode, warn when we're ignoring a command within a false
* \if-branch. But we continue on, so as to parse and discard the right
* amount of parameter text. Each individual backslash command subroutine
* is responsible for doing nothing after discarding appropriate
* arguments, if !active_branch.
*/
result = psql_scan_slash_option(scan_state, OT_SQLIDHACK, &quote, true);
if (!result)
return NULL;
if (pset.cur_cmd_interactive && !active_branch &&
!is_branching_command(cmd))
{
psql_error("\\%s command ignored; use \\endif or Ctrl-C to exit current \\if block\n",
cmd);
}
if (quote)
return result;
if (strcmp(cmd, "a") == 0)
status = exec_command_a(scan_state, active_branch);
else if (strcmp(cmd, "C") == 0)
status = exec_command_C(scan_state, active_branch);
else if (strcmp(cmd, "c") == 0 || strcmp(cmd, "connect") == 0)
status = exec_command_connect(scan_state, active_branch);
else if (strcmp(cmd, "cd") == 0)
status = exec_command_cd(scan_state, active_branch, cmd);
else if (strcmp(cmd, "conninfo") == 0)
status = exec_command_conninfo(scan_state, active_branch);
else if (pg_strcasecmp(cmd, "copy") == 0)
status = exec_command_copy(scan_state, active_branch);
else if (strcmp(cmd, "copyright") == 0)
status = exec_command_copyright(scan_state, active_branch);
else if (strcmp(cmd, "crosstabview") == 0)
status = exec_command_crosstabview(scan_state, active_branch);
else if (cmd[0] == 'd')
status = exec_command_d(scan_state, active_branch, cmd);
else if (strcmp(cmd, "e") == 0 || strcmp(cmd, "edit") == 0)
status = exec_command_edit(scan_state, active_branch,
query_buf, previous_buf);
else if (strcmp(cmd, "ef") == 0)
status = exec_command_ef(scan_state, active_branch, query_buf);
else if (strcmp(cmd, "ev") == 0)
status = exec_command_ev(scan_state, active_branch, query_buf);
else if (strcmp(cmd, "echo") == 0 || strcmp(cmd, "qecho") == 0)
status = exec_command_echo(scan_state, active_branch, cmd);
else if (strcmp(cmd, "elif") == 0)
status = exec_command_elif(scan_state, cstack, query_buf);
else if (strcmp(cmd, "else") == 0)
status = exec_command_else(scan_state, cstack, query_buf);
else if (strcmp(cmd, "endif") == 0)
status = exec_command_endif(scan_state, cstack, query_buf);
else if (strcmp(cmd, "encoding") == 0)
status = exec_command_encoding(scan_state, active_branch);
else if (strcmp(cmd, "errverbose") == 0)
status = exec_command_errverbose(scan_state, active_branch);
else if (strcmp(cmd, "f") == 0)
status = exec_command_f(scan_state, active_branch);
else if (strcmp(cmd, "g") == 0 || strcmp(cmd, "gx") == 0)
status = exec_command_g(scan_state, active_branch, cmd);
else if (strcmp(cmd, "gexec") == 0)
status = exec_command_gexec(scan_state, active_branch);
else if (strcmp(cmd, "gset") == 0)
status = exec_command_gset(scan_state, active_branch);
else if (strcmp(cmd, "h") == 0 || strcmp(cmd, "help") == 0)
status = exec_command_help(scan_state, active_branch);
else if (strcmp(cmd, "H") == 0 || strcmp(cmd, "html") == 0)
status = exec_command_html(scan_state, active_branch);
else if (strcmp(cmd, "i") == 0 || strcmp(cmd, "include") == 0 ||
strcmp(cmd, "ir") == 0 || strcmp(cmd, "include_relative") == 0)
status = exec_command_include(scan_state, active_branch, cmd);
else if (strcmp(cmd, "if") == 0)
status = exec_command_if(scan_state, cstack, query_buf);
else if (strcmp(cmd, "l") == 0 || strcmp(cmd, "list") == 0 ||
strcmp(cmd, "l+") == 0 || strcmp(cmd, "list+") == 0)
status = exec_command_list(scan_state, active_branch, cmd);
else if (strncmp(cmd, "lo_", 3) == 0)
status = exec_command_lo(scan_state, active_branch, cmd);
else if (strcmp(cmd, "o") == 0 || strcmp(cmd, "out") == 0)
status = exec_command_out(scan_state, active_branch);
else if (strcmp(cmd, "p") == 0 || strcmp(cmd, "print") == 0)
status = exec_command_print(scan_state, active_branch, query_buf);
else if (strcmp(cmd, "password") == 0)
status = exec_command_password(scan_state, active_branch);
else if (strcmp(cmd, "prompt") == 0)
status = exec_command_prompt(scan_state, active_branch, cmd);
else if (strcmp(cmd, "pset") == 0)
status = exec_command_pset(scan_state, active_branch);
else if (strcmp(cmd, "q") == 0 || strcmp(cmd, "quit") == 0)
status = exec_command_quit(scan_state, active_branch);
else if (strcmp(cmd, "r") == 0 || strcmp(cmd, "reset") == 0)
status = exec_command_reset(scan_state, active_branch, query_buf);
else if (strcmp(cmd, "s") == 0)
status = exec_command_s(scan_state, active_branch);
else if (strcmp(cmd, "set") == 0)
status = exec_command_set(scan_state, active_branch);
else if (strcmp(cmd, "setenv") == 0)
status = exec_command_setenv(scan_state, active_branch, cmd);
else if (strcmp(cmd, "sf") == 0 || strcmp(cmd, "sf+") == 0)
status = exec_command_sf(scan_state, active_branch, cmd);
else if (strcmp(cmd, "sv") == 0 || strcmp(cmd, "sv+") == 0)
status = exec_command_sv(scan_state, active_branch, cmd);
else if (strcmp(cmd, "t") == 0)
status = exec_command_t(scan_state, active_branch);
else if (strcmp(cmd, "T") == 0)
status = exec_command_T(scan_state, active_branch);
else if (strcmp(cmd, "timing") == 0)
status = exec_command_timing(scan_state, active_branch);
else if (strcmp(cmd, "unset") == 0)
status = exec_command_unset(scan_state, active_branch, cmd);
else if (strcmp(cmd, "w") == 0 || strcmp(cmd, "write") == 0)
status = exec_command_write(scan_state, active_branch, cmd,
query_buf, previous_buf);
else if (strcmp(cmd, "watch") == 0)
status = exec_command_watch(scan_state, active_branch,
query_buf, previous_buf);
else if (strcmp(cmd, "x") == 0)
status = exec_command_x(scan_state, active_branch);
else if (strcmp(cmd, "z") == 0)
status = exec_command_z(scan_state, active_branch);
else if (strcmp(cmd, "!") == 0)
status = exec_command_shell_escape(scan_state, active_branch);
else if (strcmp(cmd, "?") == 0)
status = exec_command_slash_command_help(scan_state, active_branch);
else
status = PSQL_CMD_UNKNOWN;
if (*result == '\0' || strcmp(result, "-") == 0)
return NULL;
/*
* All the commands that return PSQL_CMD_SEND want to execute previous_buf
* if query_buf is empty. For convenience we implement that here, not in
* the individual command subroutines.
*/
if (status == PSQL_CMD_SEND)
copy_previous_query(query_buf, previous_buf);
return result;
return status;
}
/*
* Subroutine to actually try to execute a backslash command.
* \a -- toggle field alignment
*
* This makes little sense but we keep it around.
*/
static backslashResult
exec_command(const char *cmd,
PsqlScanState scan_state,
PQExpBuffer query_buf)
exec_command_a(PsqlScanState scan_state, bool active_branch)
{
bool success = true; /* indicate here if the command ran ok or
* failed */
backslashResult status = PSQL_CMD_SKIP_LINE;
bool success = true;
/*
* \a -- toggle field alignment This makes little sense but we keep it
* around.
*/
if (strcmp(cmd, "a") == 0)
if (active_branch)
{
if (pset.popt.topt.format != PRINT_ALIGNED)
success = do_pset("format", "aligned", &pset.popt, pset.quiet);
......@@ -216,8 +438,18 @@ exec_command(const char *cmd,
success = do_pset("format", "unaligned", &pset.popt, pset.quiet);
}
/* \C -- override table title (formerly change HTML caption) */
else if (strcmp(cmd, "C") == 0)
return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR;
}
/*
* \C -- override table title (formerly change HTML caption)
*/
static backslashResult
exec_command_C(PsqlScanState scan_state, bool active_branch)
{
bool success = true;
if (active_branch)
{
char *opt = psql_scan_slash_option(scan_state,
OT_NORMAL, NULL, true);
......@@ -225,20 +457,32 @@ exec_command(const char *cmd,
success = do_pset("title", opt, &pset.popt, pset.quiet);
free(opt);
}
else
ignore_slash_options(scan_state);
/*
return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR;
}
/*
* \c or \connect -- connect to database using the specified parameters.
*
* \c [-reuse-previous=BOOL] dbname user host port
*
* Specifying a parameter as '-' is equivalent to omitting it. Examples:
*
* \c - - hst Connect to current database on current port of host
* "hst" as current user. \c - usr - prt Connect to current database on
* "prt" port of current host as user "usr". \c dbs Connect to
* "dbs" database on current port of current host as current user.
* \c - - hst Connect to current database on current port of
* host "hst" as current user.
* \c - usr - prt Connect to current database on port "prt" of current host
* as user "usr".
* \c dbs Connect to database "dbs" on current port of current host
* as current user.
*/
else if (strcmp(cmd, "c") == 0 || strcmp(cmd, "connect") == 0)
static backslashResult
exec_command_connect(PsqlScanState scan_state, bool active_branch)
{
bool success = true;
if (active_branch)
{
static const char prefix[] = "-reuse-previous=";
char *opt1,
......@@ -277,9 +521,21 @@ exec_command(const char *cmd,
}
free(opt1);
}
else
ignore_slash_options(scan_state);
/* \cd */
else if (strcmp(cmd, "cd") == 0)
return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR;
}
/*
* \cd -- change directory
*/
static backslashResult
exec_command_cd(PsqlScanState scan_state, bool active_branch, const char *cmd)
{
bool success = true;
if (active_branch)
{
char *opt = psql_scan_slash_option(scan_state,
OT_NORMAL, NULL, true);
......@@ -323,9 +579,19 @@ exec_command(const char *cmd,
if (opt)
free(opt);
}
else
ignore_slash_options(scan_state);
/* \conninfo -- display information about the current connection */
else if (strcmp(cmd, "conninfo") == 0)
return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR;
}
/*
* \conninfo -- display information about the current connection
*/
static backslashResult
exec_command_conninfo(PsqlScanState scan_state, bool active_branch)
{
if (active_branch)
{
char *db = PQdb(pset.db);
......@@ -366,8 +632,18 @@ exec_command(const char *cmd,
}
}
/* \copy */
else if (pg_strcasecmp(cmd, "copy") == 0)
return PSQL_CMD_SKIP_LINE;
}
/*
* \copy -- run a COPY command
*/
static backslashResult
exec_command_copy(PsqlScanState scan_state, bool active_branch)
{
bool success = true;
if (active_branch)
{
char *opt = psql_scan_slash_option(scan_state,
OT_WHOLE_LINE, NULL, false);
......@@ -375,13 +651,33 @@ exec_command(const char *cmd,
success = do_copy(opt);
free(opt);
}
else
ignore_slash_whole_line(scan_state);
/* \copyright */
else if (strcmp(cmd, "copyright") == 0)
return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR;
}
/*
* \copyright -- print copyright notice
*/
static backslashResult
exec_command_copyright(PsqlScanState scan_state, bool active_branch)
{
if (active_branch)
print_copyright();
/* \crosstabview -- execute a query and display results in crosstab */
else if (strcmp(cmd, "crosstabview") == 0)
return PSQL_CMD_SKIP_LINE;
}
/*
* \crosstabview -- execute a query and display results in crosstab
*/
static backslashResult
exec_command_crosstabview(PsqlScanState scan_state, bool active_branch)
{
backslashResult status = PSQL_CMD_SKIP_LINE;
if (active_branch)
{
int i;
......@@ -391,9 +687,22 @@ exec_command(const char *cmd,
pset.crosstab_flag = true;
status = PSQL_CMD_SEND;
}
else
ignore_slash_options(scan_state);
/* \d* commands */
else if (cmd[0] == 'd')
return status;
}
/*
* \d* commands
*/
static backslashResult
exec_command_d(PsqlScanState scan_state, bool active_branch, const char *cmd)
{
backslashResult status = PSQL_CMD_SKIP_LINE;
bool success = true;
if (active_branch)
{
char *pattern;
bool show_verbose,
......@@ -502,7 +811,7 @@ exec_command(const char *cmd,
success = listDbRoleSettings(pattern, pattern2);
}
else
success = PSQL_CMD_UNKNOWN;
status = PSQL_CMD_UNKNOWN;
break;
case 'R':
switch (cmd[2])
......@@ -580,13 +889,26 @@ exec_command(const char *cmd,
if (pattern)
free(pattern);
}
else
ignore_slash_options(scan_state);
if (!success)
status = PSQL_CMD_ERROR;
/*
* \e or \edit -- edit the current query buffer, or edit a file and make
* it the query buffer
return status;
}
/*
* \e or \edit -- edit the current query buffer, or edit a file and
* make it the query buffer
*/
else if (strcmp(cmd, "e") == 0 || strcmp(cmd, "edit") == 0)
static backslashResult
exec_command_edit(PsqlScanState scan_state, bool active_branch,
PQExpBuffer query_buf, PQExpBuffer previous_buf)
{
backslashResult status = PSQL_CMD_SKIP_LINE;
if (active_branch)
{
if (!query_buf)
{
......@@ -632,6 +954,10 @@ exec_command(const char *cmd,
expand_tilde(&fname);
if (fname)
canonicalize_path(fname);
/* Applies to previous query if current buffer is empty */
copy_previous_query(query_buf, previous_buf);
if (do_edit(fname, query_buf, lineno, NULL))
status = PSQL_CMD_NEWEDIT;
else
......@@ -643,13 +969,26 @@ exec_command(const char *cmd,
free(ln);
}
}
else
ignore_slash_options(scan_state);
/*
return status;
}
/*
* \ef -- edit the named function, or present a blank CREATE FUNCTION
* template if no argument is given
*/
else if (strcmp(cmd, "ef") == 0)
static backslashResult
exec_command_ef(PsqlScanState scan_state, bool active_branch,
PQExpBuffer query_buf)
{
backslashResult status = PSQL_CMD_SKIP_LINE;
if (active_branch)
{
char *func = psql_scan_slash_option(scan_state,
OT_WHOLE_LINE, NULL, true);
int lineno = -1;
if (pset.sversion < 80400)
......@@ -668,11 +1007,8 @@ exec_command(const char *cmd,
}
else
{
char *func;
Oid foid = InvalidOid;
func = psql_scan_slash_option(scan_state,
OT_WHOLE_LINE, NULL, true);
lineno = strip_lineno_from_objdesc(func);
if (lineno == 0)
{
......@@ -725,9 +1061,6 @@ exec_command(const char *cmd,
lines++;
}
}
if (func)
free(func);
}
if (status != PSQL_CMD_ERROR)
......@@ -741,14 +1074,30 @@ exec_command(const char *cmd,
else
status = PSQL_CMD_NEWEDIT;
}
if (func)
free(func);
}
else
ignore_slash_whole_line(scan_state);
/*
* \ev -- edit the named view, or present a blank CREATE VIEW template if
* no argument is given
return status;
}
/*
* \ev -- edit the named view, or present a blank CREATE VIEW
* template if no argument is given
*/
else if (strcmp(cmd, "ev") == 0)
static backslashResult
exec_command_ev(PsqlScanState scan_state, bool active_branch,
PQExpBuffer query_buf)
{
backslashResult status = PSQL_CMD_SKIP_LINE;
if (active_branch)
{
char *view = psql_scan_slash_option(scan_state,
OT_WHOLE_LINE, NULL, true);
int lineno = -1;
if (pset.sversion < 70400)
......@@ -767,11 +1116,8 @@ exec_command(const char *cmd,
}
else
{
char *view;
Oid view_oid = InvalidOid;
view = psql_scan_slash_option(scan_state,
OT_WHOLE_LINE, NULL, true);
lineno = strip_lineno_from_objdesc(view);
if (lineno == 0)
{
......@@ -796,9 +1142,6 @@ exec_command(const char *cmd,
/* error already reported */
status = PSQL_CMD_ERROR;
}
if (view)
free(view);
}
if (status != PSQL_CMD_ERROR)
......@@ -812,10 +1155,23 @@ exec_command(const char *cmd,
else
status = PSQL_CMD_NEWEDIT;
}
if (view)
free(view);
}
else
ignore_slash_whole_line(scan_state);
/* \echo and \qecho */
else if (strcmp(cmd, "echo") == 0 || strcmp(cmd, "qecho") == 0)
return status;
}
/*
* \echo and \qecho -- echo arguments to stdout or query output
*/
static backslashResult
exec_command_echo(PsqlScanState scan_state, bool active_branch, const char *cmd)
{
if (active_branch)
{
char *value;
char quoted;
......@@ -846,11 +1202,21 @@ exec_command(const char *cmd,
if (!no_newline)
fputs("\n", fout);
}
else
ignore_slash_options(scan_state);
/* \encoding -- set/show client side encoding */
else if (strcmp(cmd, "encoding") == 0)
{
char *encoding = psql_scan_slash_option(scan_state,
return PSQL_CMD_SKIP_LINE;
}
/*
* \encoding -- set/show client side encoding
*/
static backslashResult
exec_command_encoding(PsqlScanState scan_state, bool active_branch)
{
if (active_branch)
{
char *encoding = psql_scan_slash_option(scan_state,
OT_NORMAL, NULL, false);
if (!encoding)
......@@ -874,9 +1240,19 @@ exec_command(const char *cmd,
free(encoding);
}
}
else
ignore_slash_options(scan_state);
/* \errverbose -- display verbose message from last failed query */
else if (strcmp(cmd, "errverbose") == 0)
return PSQL_CMD_SKIP_LINE;
}
/*
* \errverbose -- display verbose message from last failed query
*/
static backslashResult
exec_command_errverbose(PsqlScanState scan_state, bool active_branch)
{
if (active_branch)
{
if (pset.last_error_result)
{
......@@ -897,8 +1273,18 @@ exec_command(const char *cmd,
puts(_("There is no previous error."));
}
/* \f -- change field separator */
else if (strcmp(cmd, "f") == 0)
return PSQL_CMD_SKIP_LINE;
}
/*
* \f -- change field separator
*/
static backslashResult
exec_command_f(PsqlScanState scan_state, bool active_branch)
{
bool success = true;
if (active_branch)
{
char *fname = psql_scan_slash_option(scan_state,
OT_NORMAL, NULL, false);
......@@ -906,12 +1292,22 @@ exec_command(const char *cmd,
success = do_pset("fieldsep", fname, &pset.popt, pset.quiet);
free(fname);
}
else
ignore_slash_options(scan_state);
/*
return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR;
}
/*
* \g [filename] -- send query, optionally with output to file/pipe
* \gx [filename] -- same as \g, with expanded mode forced
*/
else if (strcmp(cmd, "g") == 0 || strcmp(cmd, "gx") == 0)
static backslashResult
exec_command_g(PsqlScanState scan_state, bool active_branch, const char *cmd)
{
backslashResult status = PSQL_CMD_SKIP_LINE;
if (active_branch)
{
char *fname = psql_scan_slash_option(scan_state,
OT_FILEPIPE, NULL, false);
......@@ -928,16 +1324,38 @@ exec_command(const char *cmd,
pset.g_expanded = true;
status = PSQL_CMD_SEND;
}
else
ignore_slash_filepipe(scan_state);
/* \gexec -- send query and execute each field of result */
else if (strcmp(cmd, "gexec") == 0)
return status;
}
/*
* \gexec -- send query and execute each field of result
*/
static backslashResult
exec_command_gexec(PsqlScanState scan_state, bool active_branch)
{
backslashResult status = PSQL_CMD_SKIP_LINE;
if (active_branch)
{
pset.gexec_flag = true;
status = PSQL_CMD_SEND;
}
/* \gset [prefix] -- send query and store result into variables */
else if (strcmp(cmd, "gset") == 0)
return status;
}
/*
* \gset [prefix] -- send query and store result into variables
*/
static backslashResult
exec_command_gset(PsqlScanState scan_state, bool active_branch)
{
backslashResult status = PSQL_CMD_SKIP_LINE;
if (active_branch)
{
char *prefix = psql_scan_slash_option(scan_state,
OT_NORMAL, NULL, false);
......@@ -952,9 +1370,19 @@ exec_command(const char *cmd,
/* gset_prefix is freed later */
status = PSQL_CMD_SEND;
}
else
ignore_slash_options(scan_state);
/* help */
else if (strcmp(cmd, "h") == 0 || strcmp(cmd, "help") == 0)
return status;
}
/*
* \help [topic] -- print help about SQL commands
*/
static backslashResult
exec_command_help(PsqlScanState scan_state, bool active_branch)
{
if (active_branch)
{
char *opt = psql_scan_slash_option(scan_state,
OT_WHOLE_LINE, NULL, false);
......@@ -973,9 +1401,21 @@ exec_command(const char *cmd,
helpSQL(opt, pset.popt.topt.pager);
free(opt);
}
else
ignore_slash_whole_line(scan_state);
/* HTML mode */
else if (strcmp(cmd, "H") == 0 || strcmp(cmd, "html") == 0)
return PSQL_CMD_SKIP_LINE;
}
/*
* \H and \html -- toggle HTML formatting
*/
static backslashResult
exec_command_html(PsqlScanState scan_state, bool active_branch)
{
bool success = true;
if (active_branch)
{
if (pset.popt.topt.format != PRINT_HTML)
success = do_pset("format", "html", &pset.popt, pset.quiet);
......@@ -983,10 +1423,18 @@ exec_command(const char *cmd,
success = do_pset("format", "aligned", &pset.popt, pset.quiet);
}
return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR;
}
/* \i and \ir include files */
else if (strcmp(cmd, "i") == 0 || strcmp(cmd, "include") == 0
|| strcmp(cmd, "ir") == 0 || strcmp(cmd, "include_relative") == 0)
/*
* \i and \ir -- include a file
*/
static backslashResult
exec_command_include(PsqlScanState scan_state, bool active_branch, const char *cmd)
{
bool success = true;
if (active_branch)
{
char *fname = psql_scan_slash_option(scan_state,
OT_NORMAL, NULL, true);
......@@ -1007,10 +1455,254 @@ exec_command(const char *cmd,
free(fname);
}
}
else
ignore_slash_options(scan_state);
/* \l is list databases */
else if (strcmp(cmd, "l") == 0 || strcmp(cmd, "list") == 0 ||
strcmp(cmd, "l+") == 0 || strcmp(cmd, "list+") == 0)
return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR;
}
/*
* \if <expr> -- beginning of an \if..\endif block
*
* <expr> is parsed as a boolean expression. Invalid expressions will emit a
* warning and be treated as false. Statements that follow a false expression
* will be parsed but ignored. Note that in the case where an \if statement
* is itself within an inactive section of a block, then the entire inner
* \if..\endif block will be parsed but ignored.
*/
static backslashResult
exec_command_if(PsqlScanState scan_state, ConditionalStack cstack,
PQExpBuffer query_buf)
{
if (conditional_active(cstack))
{
/*
* First, push a new active stack entry; this ensures that the lexer
* will perform variable substitution and backtick evaluation while
* scanning the expression. (That should happen anyway, since we know
* we're in an active outer branch, but let's be sure.)
*/
conditional_stack_push(cstack, IFSTATE_TRUE);
/* Remember current query state in case we need to restore later */
save_query_text_state(scan_state, cstack, query_buf);
/*
* Evaluate the expression; if it's false, change to inactive state.
*/
if (!is_true_boolean_expression(scan_state, "\\if expression"))
conditional_stack_poke(cstack, IFSTATE_FALSE);
}
else
{
/*
* We're within an inactive outer branch, so this entire \if block
* will be ignored. We don't want to evaluate the expression, so push
* the "ignored" stack state before scanning it.
*/
conditional_stack_push(cstack, IFSTATE_IGNORED);
/* Remember current query state in case we need to restore later */
save_query_text_state(scan_state, cstack, query_buf);
ignore_boolean_expression(scan_state);
}
return PSQL_CMD_SKIP_LINE;
}
/*
* \elif <expr> -- alternative branch in an \if..\endif block
*
* <expr> is evaluated the same as in \if <expr>.
*/
static backslashResult
exec_command_elif(PsqlScanState scan_state, ConditionalStack cstack,
PQExpBuffer query_buf)
{
bool success = true;
switch (conditional_stack_peek(cstack))
{
case IFSTATE_TRUE:
/*
* Just finished active branch of this \if block. Update saved
* state so we will keep whatever data was put in query_buf by the
* active branch.
*/
save_query_text_state(scan_state, cstack, query_buf);
/*
* Discard \elif expression and ignore the rest until \endif.
* Switch state before reading expression to ensure proper lexer
* behavior.
*/
conditional_stack_poke(cstack, IFSTATE_IGNORED);
ignore_boolean_expression(scan_state);
break;
case IFSTATE_FALSE:
/*
* Discard any query text added by the just-skipped branch.
*/
discard_query_text(scan_state, cstack, query_buf);
/*
* Have not yet found a true expression in this \if block, so this
* might be the first. We have to change state before examining
* the expression, or the lexer won't do the right thing.
*/
conditional_stack_poke(cstack, IFSTATE_TRUE);
if (!is_true_boolean_expression(scan_state, "\\elif expression"))
conditional_stack_poke(cstack, IFSTATE_FALSE);
break;
case IFSTATE_IGNORED:
/*
* Discard any query text added by the just-skipped branch.
*/
discard_query_text(scan_state, cstack, query_buf);
/*
* Skip expression and move on. Either the \if block already had
* an active section, or whole block is being skipped.
*/
ignore_boolean_expression(scan_state);
break;
case IFSTATE_ELSE_TRUE:
case IFSTATE_ELSE_FALSE:
psql_error("\\elif: cannot occur after \\else\n");
success = false;
break;
case IFSTATE_NONE:
/* no \if to elif from */
psql_error("\\elif: no matching \\if\n");
success = false;
break;
}
return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR;
}
/*
* \else -- final alternative in an \if..\endif block
*
* Statements within an \else branch will only be executed if
* all previous \if and \elif expressions evaluated to false
* and the block was not itself being ignored.
*/
static backslashResult
exec_command_else(PsqlScanState scan_state, ConditionalStack cstack,
PQExpBuffer query_buf)
{
bool success = true;
switch (conditional_stack_peek(cstack))
{
case IFSTATE_TRUE:
/*
* Just finished active branch of this \if block. Update saved
* state so we will keep whatever data was put in query_buf by the
* active branch.
*/
save_query_text_state(scan_state, cstack, query_buf);
/* Now skip the \else branch */
conditional_stack_poke(cstack, IFSTATE_ELSE_FALSE);
break;
case IFSTATE_FALSE:
/*
* Discard any query text added by the just-skipped branch.
*/
discard_query_text(scan_state, cstack, query_buf);
/*
* We've not found any true \if or \elif expression, so execute
* the \else branch.
*/
conditional_stack_poke(cstack, IFSTATE_ELSE_TRUE);
break;
case IFSTATE_IGNORED:
/*
* Discard any query text added by the just-skipped branch.
*/
discard_query_text(scan_state, cstack, query_buf);
/*
* Either we previously processed the active branch of this \if,
* or the whole \if block is being skipped. Either way, skip the
* \else branch.
*/
conditional_stack_poke(cstack, IFSTATE_ELSE_FALSE);
break;
case IFSTATE_ELSE_TRUE:
case IFSTATE_ELSE_FALSE:
psql_error("\\else: cannot occur after \\else\n");
success = false;
break;
case IFSTATE_NONE:
/* no \if to else from */
psql_error("\\else: no matching \\if\n");
success = false;
break;
}
return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR;
}
/*
* \endif -- ends an \if...\endif block
*/
static backslashResult
exec_command_endif(PsqlScanState scan_state, ConditionalStack cstack,
PQExpBuffer query_buf)
{
bool success = true;
switch (conditional_stack_peek(cstack))
{
case IFSTATE_TRUE:
case IFSTATE_ELSE_TRUE:
/* Close the \if block, keeping the query text */
success = conditional_stack_pop(cstack);
Assert(success);
break;
case IFSTATE_FALSE:
case IFSTATE_IGNORED:
case IFSTATE_ELSE_FALSE:
/*
* Discard any query text added by the just-skipped branch.
*/
discard_query_text(scan_state, cstack, query_buf);
/* Close the \if block */
success = conditional_stack_pop(cstack);
Assert(success);
break;
case IFSTATE_NONE:
/* no \if to end */
psql_error("\\endif: no matching \\if\n");
success = false;
break;
}
return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR;
}
/*
* \l -- list databases
*/
static backslashResult
exec_command_list(PsqlScanState scan_state, bool active_branch, const char *cmd)
{
bool success = true;
if (active_branch)
{
char *pattern;
bool show_verbose;
......@@ -1025,11 +1717,22 @@ exec_command(const char *cmd,
if (pattern)
free(pattern);
}
else
ignore_slash_options(scan_state);
/*
* large object things
return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR;
}
/*
* \lo_* -- large object operations
*/
else if (strncmp(cmd, "lo_", 3) == 0)
static backslashResult
exec_command_lo(PsqlScanState scan_state, bool active_branch, const char *cmd)
{
backslashResult status = PSQL_CMD_SKIP_LINE;
bool success = true;
if (active_branch)
{
char *opt1,
*opt2;
......@@ -1087,10 +1790,24 @@ exec_command(const char *cmd,
free(opt1);
free(opt2);
}
else
ignore_slash_options(scan_state);
if (!success)
status = PSQL_CMD_ERROR;
/* \o -- set query output */
else if (strcmp(cmd, "o") == 0 || strcmp(cmd, "out") == 0)
return status;
}
/*
* \o -- set query output
*/
static backslashResult
exec_command_out(PsqlScanState scan_state, bool active_branch)
{
bool success = true;
if (active_branch)
{
char *fname = psql_scan_slash_option(scan_state,
OT_FILEPIPE, NULL, true);
......@@ -1099,9 +1816,20 @@ exec_command(const char *cmd,
success = setQFout(fname);
free(fname);
}
else
ignore_slash_filepipe(scan_state);
/* \p prints the current query buffer */
else if (strcmp(cmd, "p") == 0 || strcmp(cmd, "print") == 0)
return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR;
}
/*
* \p -- print the current query buffer
*/
static backslashResult
exec_command_print(PsqlScanState scan_state, bool active_branch,
PQExpBuffer query_buf)
{
if (active_branch)
{
if (query_buf && query_buf->len > 0)
puts(query_buf->data);
......@@ -1110,9 +1838,21 @@ exec_command(const char *cmd,
fflush(stdout);
}
/* \password -- set user password */
else if (strcmp(cmd, "password") == 0)
return PSQL_CMD_SKIP_LINE;
}
/*
* \password -- set user password
*/
static backslashResult
exec_command_password(PsqlScanState scan_state, bool active_branch)
{
bool success = true;
if (active_branch)
{
char *opt0 = psql_scan_slash_option(scan_state,
OT_SQLID, NULL, true);
char pw1[100];
char pw2[100];
......@@ -1126,7 +1866,6 @@ exec_command(const char *cmd,
}
else
{
char *opt0 = psql_scan_slash_option(scan_state, OT_SQLID, NULL, true);
char *user;
char *encrypted_password;
......@@ -1159,14 +1898,27 @@ exec_command(const char *cmd,
PQclear(res);
PQfreemem(encrypted_password);
}
}
if (opt0)
free(opt0);
}
}
else
ignore_slash_options(scan_state);
/* \prompt -- prompt and set variable */
else if (strcmp(cmd, "prompt") == 0)
return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR;
}
/*
* \prompt -- prompt and set variable
*/
static backslashResult
exec_command_prompt(PsqlScanState scan_state, bool active_branch,
const char *cmd)
{
bool success = true;
if (active_branch)
{
char *opt,
*prompt_text = NULL;
......@@ -1225,9 +1977,21 @@ exec_command(const char *cmd,
free(opt);
}
}
else
ignore_slash_options(scan_state);
/* \pset -- set printing parameters */
else if (strcmp(cmd, "pset") == 0)
return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR;
}
/*
* \pset -- set printing parameters
*/
static backslashResult
exec_command_pset(PsqlScanState scan_state, bool active_branch)
{
bool success = true;
if (active_branch)
{
char *opt0 = psql_scan_slash_option(scan_state,
OT_NORMAL, NULL, false);
......@@ -1267,13 +2031,34 @@ exec_command(const char *cmd,
free(opt0);
free(opt1);
}
else
ignore_slash_options(scan_state);
/* \q or \quit */
else if (strcmp(cmd, "q") == 0 || strcmp(cmd, "quit") == 0)
return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR;
}
/*
* \q or \quit -- exit psql
*/
static backslashResult
exec_command_quit(PsqlScanState scan_state, bool active_branch)
{
backslashResult status = PSQL_CMD_SKIP_LINE;
if (active_branch)
status = PSQL_CMD_TERMINATE;
/* reset(clear) the buffer */
else if (strcmp(cmd, "r") == 0 || strcmp(cmd, "reset") == 0)
return status;
}
/*
* \r -- reset (clear) the query buffer
*/
static backslashResult
exec_command_reset(PsqlScanState scan_state, bool active_branch,
PQExpBuffer query_buf)
{
if (active_branch)
{
resetPQExpBuffer(query_buf);
psql_scan_reset(scan_state);
......@@ -1281,8 +2066,18 @@ exec_command(const char *cmd,
puts(_("Query buffer reset (cleared)."));
}
/* \s save history in a file or show it on the screen */
else if (strcmp(cmd, "s") == 0)
return PSQL_CMD_SKIP_LINE;
}
/*
* \s -- save history in a file or show it on the screen
*/
static backslashResult
exec_command_s(PsqlScanState scan_state, bool active_branch)
{
bool success = true;
if (active_branch)
{
char *fname = psql_scan_slash_option(scan_state,
OT_NORMAL, NULL, true);
......@@ -1295,9 +2090,21 @@ exec_command(const char *cmd,
putchar('\n');
free(fname);
}
else
ignore_slash_options(scan_state);
/* \set -- generalized set variable/option command */
else if (strcmp(cmd, "set") == 0)
return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR;
}
/*
* \set -- set variable
*/
static backslashResult
exec_command_set(PsqlScanState scan_state, bool active_branch)
{
bool success = true;
if (active_branch)
{
char *opt0 = psql_scan_slash_option(scan_state,
OT_NORMAL, NULL, false);
......@@ -1336,10 +2143,22 @@ exec_command(const char *cmd,
}
free(opt0);
}
else
ignore_slash_options(scan_state);
return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR;
}
/*
* \setenv -- set environment variable
*/
static backslashResult
exec_command_setenv(PsqlScanState scan_state, bool active_branch,
const char *cmd)
{
bool success = true;
/* \setenv -- set environment command */
else if (strcmp(cmd, "setenv") == 0)
if (active_branch)
{
char *envvar = psql_scan_slash_option(scan_state,
OT_NORMAL, NULL, false);
......@@ -1381,9 +2200,21 @@ exec_command(const char *cmd,
free(envvar);
free(envval);
}
else
ignore_slash_options(scan_state);
/* \sf -- show a function's source code */
else if (strcmp(cmd, "sf") == 0 || strcmp(cmd, "sf+") == 0)
return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR;
}
/*
* \sf -- show a function's source code
*/
static backslashResult
exec_command_sf(PsqlScanState scan_state, bool active_branch, const char *cmd)
{
backslashResult status = PSQL_CMD_SKIP_LINE;
if (active_branch)
{
bool show_linenumbers = (strcmp(cmd, "sf+") == 0);
PQExpBuffer func_buf;
......@@ -1463,9 +2294,21 @@ exec_command(const char *cmd,
free(func);
destroyPQExpBuffer(func_buf);
}
else
ignore_slash_whole_line(scan_state);
/* \sv -- show a view's source code */
else if (strcmp(cmd, "sv") == 0 || strcmp(cmd, "sv+") == 0)
return status;
}
/*
* \sv -- show a view's source code
*/
static backslashResult
exec_command_sv(PsqlScanState scan_state, bool active_branch, const char *cmd)
{
backslashResult status = PSQL_CMD_SKIP_LINE;
if (active_branch)
{
bool show_linenumbers = (strcmp(cmd, "sv+") == 0);
PQExpBuffer view_buf;
......@@ -1539,9 +2382,21 @@ exec_command(const char *cmd,
free(view);
destroyPQExpBuffer(view_buf);
}
else
ignore_slash_whole_line(scan_state);
/* \t -- turn off headers and row count */
else if (strcmp(cmd, "t") == 0)
return status;
}
/*
* \t -- turn off table headers and row count
*/
static backslashResult
exec_command_t(PsqlScanState scan_state, bool active_branch)
{
bool success = true;
if (active_branch)
{
char *opt = psql_scan_slash_option(scan_state,
OT_NORMAL, NULL, true);
......@@ -1549,9 +2404,21 @@ exec_command(const char *cmd,
success = do_pset("tuples_only", opt, &pset.popt, pset.quiet);
free(opt);
}
else
ignore_slash_options(scan_state);
/* \T -- define html <table ...> attributes */
else if (strcmp(cmd, "T") == 0)
return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR;
}
/*
* \T -- define html <table ...> attributes
*/
static backslashResult
exec_command_T(PsqlScanState scan_state, bool active_branch)
{
bool success = true;
if (active_branch)
{
char *value = psql_scan_slash_option(scan_state,
OT_NORMAL, NULL, false);
......@@ -1559,9 +2426,21 @@ exec_command(const char *cmd,
success = do_pset("tableattr", value, &pset.popt, pset.quiet);
free(value);
}
else
ignore_slash_options(scan_state);
/* \timing -- toggle timing of queries */
else if (strcmp(cmd, "timing") == 0)
return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR;
}
/*
* \timing -- enable/disable timing of queries
*/
static backslashResult
exec_command_timing(PsqlScanState scan_state, bool active_branch)
{
bool success = true;
if (active_branch)
{
char *opt = psql_scan_slash_option(scan_state,
OT_NORMAL, NULL, false);
......@@ -1579,9 +2458,22 @@ exec_command(const char *cmd,
}
free(opt);
}
else
ignore_slash_options(scan_state);
/* \unset */
else if (strcmp(cmd, "unset") == 0)
return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR;
}
/*
* \unset -- unset variable
*/
static backslashResult
exec_command_unset(PsqlScanState scan_state, bool active_branch,
const char *cmd)
{
bool success = true;
if (active_branch)
{
char *opt = psql_scan_slash_option(scan_state,
OT_NORMAL, NULL, false);
......@@ -1596,13 +2488,28 @@ exec_command(const char *cmd,
free(opt);
}
else
ignore_slash_options(scan_state);
/* \w -- write query buffer to file */
else if (strcmp(cmd, "w") == 0 || strcmp(cmd, "write") == 0)
return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR;
}
/*
* \w -- write query buffer to file
*/
static backslashResult
exec_command_write(PsqlScanState scan_state, bool active_branch,
const char *cmd,
PQExpBuffer query_buf, PQExpBuffer previous_buf)
{
backslashResult status = PSQL_CMD_SKIP_LINE;
if (active_branch)
{
char *fname = psql_scan_slash_option(scan_state,
OT_FILEPIPE, NULL, true);
FILE *fd = NULL;
bool is_pipe = false;
char *fname = NULL;
if (!query_buf)
{
......@@ -1611,17 +2518,14 @@ exec_command(const char *cmd,
}
else
{
fname = psql_scan_slash_option(scan_state,
OT_FILEPIPE, NULL, true);
expand_tilde(&fname);
if (!fname)
{
psql_error("\\%s: missing required argument\n", cmd);
success = false;
status = PSQL_CMD_ERROR;
}
else
{
expand_tilde(&fname);
if (fname[0] == '|')
{
is_pipe = true;
......@@ -1636,7 +2540,7 @@ exec_command(const char *cmd,
if (!fd)
{
psql_error("%s: %s\n", fname, strerror(errno));
success = false;
status = PSQL_CMD_ERROR;
}
}
}
......@@ -1647,6 +2551,9 @@ exec_command(const char *cmd,
if (query_buf && query_buf->len > 0)
fprintf(fd, "%s\n", query_buf->data);
/* Applies to previous query if current buffer is empty */
else if (previous_buf && previous_buf->len > 0)
fprintf(fd, "%s\n", previous_buf->data);
if (is_pipe)
result = pclose(fd);
......@@ -1656,7 +2563,7 @@ exec_command(const char *cmd,
if (result == EOF)
{
psql_error("%s: %s\n", fname, strerror(errno));
success = false;
status = PSQL_CMD_ERROR;
}
}
......@@ -1665,9 +2572,22 @@ exec_command(const char *cmd,
free(fname);
}
else
ignore_slash_filepipe(scan_state);
/* \watch -- execute a query every N seconds */
else if (strcmp(cmd, "watch") == 0)
return status;
}
/*
* \watch -- execute a query every N seconds
*/
static backslashResult
exec_command_watch(PsqlScanState scan_state, bool active_branch,
PQExpBuffer query_buf, PQExpBuffer previous_buf)
{
bool success = true;
if (active_branch)
{
char *opt = psql_scan_slash_option(scan_state,
OT_NORMAL, NULL, true);
......@@ -1682,15 +2602,30 @@ exec_command(const char *cmd,
free(opt);
}
/* Applies to previous query if current buffer is empty */
copy_previous_query(query_buf, previous_buf);
success = do_watch(query_buf, sleep);
/* Reset the query buffer as though for \r */
resetPQExpBuffer(query_buf);
psql_scan_reset(scan_state);
}
else
ignore_slash_options(scan_state);
/* \x -- set or toggle expanded table representation */
else if (strcmp(cmd, "x") == 0)
return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR;
}
/*
* \x -- set or toggle expanded table representation
*/
static backslashResult
exec_command_x(PsqlScanState scan_state, bool active_branch)
{
bool success = true;
if (active_branch)
{
char *opt = psql_scan_slash_option(scan_state,
OT_NORMAL, NULL, true);
......@@ -1698,9 +2633,21 @@ exec_command(const char *cmd,
success = do_pset("expanded", opt, &pset.popt, pset.quiet);
free(opt);
}
else
ignore_slash_options(scan_state);
/* \z -- list table rights (equivalent to \dp) */
else if (strcmp(cmd, "z") == 0)
return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR;
}
/*
* \z -- list table privileges (equivalent to \dp)
*/
static backslashResult
exec_command_z(PsqlScanState scan_state, bool active_branch)
{
bool success = true;
if (active_branch)
{
char *pattern = psql_scan_slash_option(scan_state,
OT_NORMAL, NULL, true);
......@@ -1709,9 +2656,21 @@ exec_command(const char *cmd,
if (pattern)
free(pattern);
}
else
ignore_slash_options(scan_state);
/* \! -- shell escape */
else if (strcmp(cmd, "!") == 0)
return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR;
}
/*
* \! -- execute shell command
*/
static backslashResult
exec_command_shell_escape(PsqlScanState scan_state, bool active_branch)
{
bool success = true;
if (active_branch)
{
char *opt = psql_scan_slash_option(scan_state,
OT_WHOLE_LINE, NULL, false);
......@@ -1719,9 +2678,19 @@ exec_command(const char *cmd,
success = do_shell(opt);
free(opt);
}
else
ignore_slash_whole_line(scan_state);
/* \? -- slash command help */
else if (strcmp(cmd, "?") == 0)
return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR;
}
/*
* \? -- print help about backslash commands
*/
static backslashResult
exec_command_slash_command_help(PsqlScanState scan_state, bool active_branch)
{
if (active_branch)
{
char *opt0 = psql_scan_slash_option(scan_state,
OT_NORMAL, NULL, false);
......@@ -1734,34 +2703,231 @@ exec_command(const char *cmd,
helpVariables(pset.popt.topt.pager);
else
slashUsage(pset.popt.topt.pager);
if (opt0)
free(opt0);
}
else
ignore_slash_options(scan_state);
return PSQL_CMD_SKIP_LINE;
}
#if 0
/*
* Read and interpret an argument to the \connect slash command.
*/
static char *
read_connect_arg(PsqlScanState scan_state)
{
char *result;
char quote;
/*
* These commands don't do anything. I just use them to test the parser.
* Ideally we should treat the arguments as SQL identifiers. But for
* backwards compatibility with 7.2 and older pg_dump files, we have to
* take unquoted arguments verbatim (don't downcase them). For now,
* double-quoted arguments may be stripped of double quotes (as if SQL
* identifiers). By 7.4 or so, pg_dump files can be expected to
* double-quote all mixed-case \connect arguments, and then we can get rid
* of OT_SQLIDHACK.
*/
else if (strcmp(cmd, "void") == 0 || strcmp(cmd, "#") == 0)
{
int i = 0;
result = psql_scan_slash_option(scan_state, OT_SQLIDHACK, &quote, true);
if (!result)
return NULL;
if (quote)
return result;
if (*result == '\0' || strcmp(result, "-") == 0)
return NULL;
return result;
}
/*
* Read a boolean expression, return it as a PQExpBuffer string.
*
* Note: anything more or less than one token will certainly fail to be
* parsed by ParseVariableBool, so we don't worry about complaining here.
* This routine's return data structure will need to be rethought anyway
* to support likely future extensions such as "\if defined VARNAME".
*/
static PQExpBuffer
gather_boolean_expression(PsqlScanState scan_state)
{
PQExpBuffer exp_buf = createPQExpBuffer();
int num_options = 0;
char *value;
/* collect all arguments for the conditional command into exp_buf */
while ((value = psql_scan_slash_option(scan_state,
OT_NORMAL, NULL, true)))
OT_NORMAL, NULL, false)) != NULL)
{
psql_error("+ opt(%d) = |%s|\n", i++, value);
/* add spaces between tokens */
if (num_options > 0)
appendPQExpBufferChar(exp_buf, ' ');
appendPQExpBufferStr(exp_buf, value);
num_options++;
free(value);
}
}
#endif
else
status = PSQL_CMD_UNKNOWN;
return exp_buf;
}
if (!success)
status = PSQL_CMD_ERROR;
/*
* Read a boolean expression, return true if the expression
* was a valid boolean expression that evaluated to true.
* Otherwise return false.
*
* Note: conditional stack's top state must be active, else lexer will
* fail to expand variables and backticks.
*/
static bool
is_true_boolean_expression(PsqlScanState scan_state, const char *name)
{
PQExpBuffer buf = gather_boolean_expression(scan_state);
bool value = false;
bool success = ParseVariableBool(buf->data, name, &value);
return status;
destroyPQExpBuffer(buf);
return success && value;
}
/*
* Read a boolean expression, but do nothing with it.
*
* Note: conditional stack's top state must be INACTIVE, else lexer will
* expand variables and backticks, which we do not want here.
*/
static void
ignore_boolean_expression(PsqlScanState scan_state)
{
PQExpBuffer buf = gather_boolean_expression(scan_state);
destroyPQExpBuffer(buf);
}
/*
* Read and discard "normal" slash command options.
*
* This should be used for inactive-branch processing of any slash command
* that eats one or more OT_NORMAL, OT_SQLID, or OT_SQLIDHACK parameters.
* We don't need to worry about exactly how many it would eat, since the
* cleanup logic in HandleSlashCmds would silently discard any extras anyway.
*/
static void
ignore_slash_options(PsqlScanState scan_state)
{
char *arg;
while ((arg = psql_scan_slash_option(scan_state,
OT_NORMAL, NULL, false)) != NULL)
free(arg);
}
/*
* Read and discard FILEPIPE slash command argument.
*
* This *MUST* be used for inactive-branch processing of any slash command
* that takes an OT_FILEPIPE option. Otherwise we might consume a different
* amount of option text in active and inactive cases.
*/
static void
ignore_slash_filepipe(PsqlScanState scan_state)
{
char *arg = psql_scan_slash_option(scan_state,
OT_FILEPIPE, NULL, false);
if (arg)
free(arg);
}
/*
* Read and discard whole-line slash command argument.
*
* This *MUST* be used for inactive-branch processing of any slash command
* that takes an OT_WHOLE_LINE option. Otherwise we might consume a different
* amount of option text in active and inactive cases.
*/
static void
ignore_slash_whole_line(PsqlScanState scan_state)
{
char *arg = psql_scan_slash_option(scan_state,
OT_WHOLE_LINE, NULL, false);
if (arg)
free(arg);
}
/*
* Return true if the command given is a branching command.
*/
static bool
is_branching_command(const char *cmd)
{
return (strcmp(cmd, "if") == 0 ||
strcmp(cmd, "elif") == 0 ||
strcmp(cmd, "else") == 0 ||
strcmp(cmd, "endif") == 0);
}
/*
* Prepare to possibly restore query buffer to its current state
* (cf. discard_query_text).
*
* We need to remember the length of the query buffer, and the lexer's
* notion of the parenthesis nesting depth.
*/
static void
save_query_text_state(PsqlScanState scan_state, ConditionalStack cstack,
PQExpBuffer query_buf)
{
if (query_buf)
conditional_stack_set_query_len(cstack, query_buf->len);
conditional_stack_set_paren_depth(cstack,
psql_scan_get_paren_depth(scan_state));
}
/*
* Discard any query text absorbed during an inactive conditional branch.
*
* We must discard data that was appended to query_buf during an inactive
* \if branch. We don't have to do anything there if there's no query_buf.
*
* Also, reset the lexer state to the same paren depth there was before.
* (The rest of its state doesn't need attention, since we could not be
* inside a comment or literal or partial token.)
*/
static void
discard_query_text(PsqlScanState scan_state, ConditionalStack cstack,
PQExpBuffer query_buf)
{
if (query_buf)
{
int new_len = conditional_stack_get_query_len(cstack);
Assert(new_len >= 0 && new_len <= query_buf->len);
query_buf->len = new_len;
query_buf->data[new_len] = '\0';
}
psql_scan_set_paren_depth(scan_state,
conditional_stack_get_paren_depth(cstack));
}
/*
* If query_buf is empty, copy previous_buf into it.
*
* This is used by various slash commands for which re-execution of a
* previous query is a common usage. For convenience, we allow the
* case of query_buf == NULL (and do nothing).
*/
static void
copy_previous_query(PQExpBuffer query_buf, PQExpBuffer previous_buf)
{
if (query_buf && query_buf->len == 0)
appendPQExpBufferStr(query_buf, previous_buf->data);
}
/*
......
......@@ -10,6 +10,7 @@
#include "fe_utils/print.h"
#include "fe_utils/psqlscan.h"
#include "conditional.h"
typedef enum _backslashResult
......@@ -25,7 +26,9 @@ typedef enum _backslashResult
extern backslashResult HandleSlashCmds(PsqlScanState scan_state,
PQExpBuffer query_buf);
ConditionalStack cstack,
PQExpBuffer query_buf,
PQExpBuffer previous_buf);
extern int process_file(char *filename, bool use_relative_path);
......
......@@ -121,7 +121,8 @@ setQFout(const char *fname)
* (Failure in escaping should lead to returning NULL.)
*
* "passthrough" is the pointer previously given to psql_scan_set_passthrough.
* psql currently doesn't use this.
* 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,
......@@ -130,6 +131,10 @@ psql_get_variable(const char *varname, bool escape, bool as_ident,
char *result;
const char *value;
/* In an inactive \if branch, suppress all variable substitutions */
if (passthrough && !conditional_active((ConditionalStack) passthrough))
return NULL;
value = GetVariable(pset.vars, varname);
if (!value)
return NULL;
......
/*
* psql - the PostgreSQL interactive terminal
*
* Copyright (c) 2000-2017, PostgreSQL Global Development Group
*
* src/bin/psql/conditional.c
*/
#include "postgres_fe.h"
#include "conditional.h"
/*
* create stack
*/
ConditionalStack
conditional_stack_create(void)
{
ConditionalStack cstack = pg_malloc(sizeof(ConditionalStackData));
cstack->head = NULL;
return cstack;
}
/*
* destroy stack
*/
void
conditional_stack_destroy(ConditionalStack cstack)
{
while (conditional_stack_pop(cstack))
continue;
free(cstack);
}
/*
* Create a new conditional branch.
*/
void
conditional_stack_push(ConditionalStack cstack, ifState new_state)
{
IfStackElem *p = (IfStackElem *) pg_malloc(sizeof(IfStackElem));
p->if_state = new_state;
p->query_len = -1;
p->paren_depth = -1;
p->next = cstack->head;
cstack->head = p;
}
/*
* Destroy the topmost conditional branch.
* Returns false if there was no branch to end.
*/
bool
conditional_stack_pop(ConditionalStack cstack)
{
IfStackElem *p = cstack->head;
if (!p)
return false;
cstack->head = cstack->head->next;
free(p);
return true;
}
/*
* Fetch the current state of the top of the stack.
*/
ifState
conditional_stack_peek(ConditionalStack cstack)
{
if (conditional_stack_empty(cstack))
return IFSTATE_NONE;
return cstack->head->if_state;
}
/*
* Change the state of the topmost branch.
* Returns false if there was no branch state to set.
*/
bool
conditional_stack_poke(ConditionalStack cstack, ifState new_state)
{
if (conditional_stack_empty(cstack))
return false;
cstack->head->if_state = new_state;
return true;
}
/*
* True if there are no active \if-blocks.
*/
bool
conditional_stack_empty(ConditionalStack cstack)
{
return cstack->head == NULL;
}
/*
* True if we should execute commands normally; that is, the current
* conditional branch is active, or there is no open \if block.
*/
bool
conditional_active(ConditionalStack cstack)
{
ifState s = conditional_stack_peek(cstack);
return s == IFSTATE_NONE || s == IFSTATE_TRUE || s == IFSTATE_ELSE_TRUE;
}
/*
* Save current query buffer length in topmost stack entry.
*/
void
conditional_stack_set_query_len(ConditionalStack cstack, int len)
{
Assert(!conditional_stack_empty(cstack));
cstack->head->query_len = len;
}
/*
* Fetch last-recorded query buffer length from topmost stack entry.
* Will return -1 if no stack or it was never saved.
*/
int
conditional_stack_get_query_len(ConditionalStack cstack)
{
if (conditional_stack_empty(cstack))
return -1;
return cstack->head->query_len;
}
/*
* Save current parenthesis nesting depth in topmost stack entry.
*/
void
conditional_stack_set_paren_depth(ConditionalStack cstack, int depth)
{
Assert(!conditional_stack_empty(cstack));
cstack->head->paren_depth = depth;
}
/*
* Fetch last-recorded parenthesis nesting depth from topmost stack entry.
* Will return -1 if no stack or it was never saved.
*/
int
conditional_stack_get_paren_depth(ConditionalStack cstack)
{
if (conditional_stack_empty(cstack))
return -1;
return cstack->head->paren_depth;
}
/*
* psql - the PostgreSQL interactive terminal
*
* Copyright (c) 2000-2017, PostgreSQL Global Development Group
*
* src/bin/psql/conditional.h
*/
#ifndef CONDITIONAL_H
#define CONDITIONAL_H
/*
* Possible states of a single level of \if block.
*/
typedef enum ifState
{
IFSTATE_NONE = 0, /* not currently in an \if block */
IFSTATE_TRUE, /* currently in an \if or \elif that is true
* and all parent branches (if any) are true */
IFSTATE_FALSE, /* currently in an \if or \elif that is false
* but no true branch has yet been seen, and
* all parent branches (if any) are true */
IFSTATE_IGNORED, /* currently in an \elif that follows a true
* branch, or the whole \if is a child of a
* false parent branch */
IFSTATE_ELSE_TRUE, /* currently in an \else that is true and all
* parent branches (if any) are true */
IFSTATE_ELSE_FALSE /* currently in an \else that is false or
* ignored */
} ifState;
/*
* The state of nested \ifs is stored in a stack.
*
* query_len is used to determine what accumulated text to throw away at the
* end of an inactive branch. (We could, perhaps, teach the lexer to not add
* stuff to the query buffer in the first place when inside an inactive branch;
* but that would be very invasive.) We also need to save and restore the
* lexer's parenthesis nesting depth when throwing away text. (We don't need
* to save and restore any of its other state, such as comment nesting depth,
* because a backslash command could never appear inside a comment or SQL
* literal.)
*/
typedef struct IfStackElem
{
ifState if_state; /* current state, see enum above */
int query_len; /* length of query_buf at last branch start */
int paren_depth; /* parenthesis depth at last branch start */
struct IfStackElem *next; /* next surrounding \if, if any */
} IfStackElem;
typedef struct ConditionalStackData
{
IfStackElem *head;
} ConditionalStackData;
typedef struct ConditionalStackData *ConditionalStack;
extern ConditionalStack conditional_stack_create(void);
extern void conditional_stack_destroy(ConditionalStack cstack);
extern void conditional_stack_push(ConditionalStack cstack, ifState new_state);
extern bool conditional_stack_pop(ConditionalStack cstack);
extern ifState conditional_stack_peek(ConditionalStack cstack);
extern bool conditional_stack_poke(ConditionalStack cstack, ifState new_state);
extern bool conditional_stack_empty(ConditionalStack cstack);
extern bool conditional_active(ConditionalStack cstack);
extern void conditional_stack_set_query_len(ConditionalStack cstack, int len);
extern int conditional_stack_get_query_len(ConditionalStack cstack);
extern void conditional_stack_set_paren_depth(ConditionalStack cstack, int depth);
extern int conditional_stack_get_paren_depth(ConditionalStack cstack);
#endif /* CONDITIONAL_H */
......@@ -552,7 +552,7 @@ handleCopyIn(PGconn *conn, FILE *copystream, bool isbinary, PGresult **res)
/* interactive input probably silly, but give one prompt anyway */
if (showprompt)
{
const char *prompt = get_prompt(PROMPT_COPY);
const char *prompt = get_prompt(PROMPT_COPY, NULL);
fputs(prompt, stdout);
fflush(stdout);
......@@ -590,7 +590,7 @@ handleCopyIn(PGconn *conn, FILE *copystream, bool isbinary, PGresult **res)
if (showprompt)
{
const char *prompt = get_prompt(PROMPT_COPY);
const char *prompt = get_prompt(PROMPT_COPY, NULL);
fputs(prompt, stdout);
fflush(stdout);
......
......@@ -167,7 +167,7 @@ slashUsage(unsigned short int pager)
* Use "psql --help=commands | wc" to count correctly. It's okay to count
* the USE_READLINE line even in builds without that.
*/
output = PageOutput(113, pager ? &(pset.popt.topt) : NULL);
output = PageOutput(122, pager ? &(pset.popt.topt) : NULL);
fprintf(output, _("General\n"));
fprintf(output, _(" \\copyright show PostgreSQL usage and distribution terms\n"));
......@@ -210,6 +210,13 @@ slashUsage(unsigned short int pager)
fprintf(output, _(" \\qecho [STRING] write string to query output stream (see \\o)\n"));
fprintf(output, "\n");
fprintf(output, _("Conditional\n"));
fprintf(output, _(" \\if EXPR begin conditional block\n"));
fprintf(output, _(" \\elif EXPR alternative within current conditional block\n"));
fprintf(output, _(" \\else final alternative within current conditional block\n"));
fprintf(output, _(" \\endif end conditional block\n"));
fprintf(output, "\n");
fprintf(output, _("Informational\n"));
fprintf(output, _(" (options: S = show system objects, + = additional detail)\n"));
fprintf(output, _(" \\d[S+] list tables, views, and sequences\n"));
......
......@@ -35,6 +35,7 @@ int
MainLoop(FILE *source)
{
PsqlScanState scan_state; /* lexer working state */
ConditionalStack cond_stack; /* \if status stack */
volatile PQExpBuffer query_buf; /* buffer for query being accumulated */
volatile PQExpBuffer previous_buf; /* if there isn't anything in the new
* buffer yet, use this one for \e,
......@@ -50,16 +51,15 @@ MainLoop(FILE *source)
volatile promptStatus_t prompt_status = PROMPT_READY;
volatile int count_eof = 0;
volatile bool die_on_error = false;
/* Save the prior command source */
FILE *prev_cmd_source;
bool prev_cmd_interactive;
uint64 prev_lineno;
/* Save old settings */
/* Save the prior command source */
prev_cmd_source = pset.cur_cmd_source;
prev_cmd_interactive = pset.cur_cmd_interactive;
prev_lineno = pset.lineno;
/* pset.stmt_lineno does not need to be saved and restored */
/* Establish new source */
pset.cur_cmd_source = source;
......@@ -69,6 +69,8 @@ MainLoop(FILE *source)
/* Create working state */
scan_state = psql_scan_create(&psqlscan_callbacks);
cond_stack = conditional_stack_create();
psql_scan_set_passthrough(scan_state, (void *) cond_stack);
query_buf = createPQExpBuffer();
previous_buf = createPQExpBuffer();
......@@ -122,7 +124,19 @@ MainLoop(FILE *source)
cancel_pressed = false;
if (pset.cur_cmd_interactive)
{
putc('\n', stdout);
/*
* if interactive user is in an \if block, then Ctrl-C will
* exit from the innermost \if.
*/
if (!conditional_stack_empty(cond_stack))
{
psql_error("\\if: escaped\n");
conditional_stack_pop(cond_stack);
}
}
else
{
successResult = EXIT_USER;
......@@ -140,7 +154,8 @@ MainLoop(FILE *source)
/* May need to reset prompt, eg after \r command */
if (query_buf->len == 0)
prompt_status = PROMPT_READY;
line = gets_interactive(get_prompt(prompt_status), query_buf);
line = gets_interactive(get_prompt(prompt_status, cond_stack),
query_buf);
}
else
{
......@@ -286,8 +301,10 @@ MainLoop(FILE *source)
(scan_result == PSCAN_EOL && pset.singleline))
{
/*
* Save query in history. We use history_buf to accumulate
* multi-line queries into a single history entry.
* Save line in history. We use history_buf to accumulate
* multi-line queries into a single history entry. Note that
* history accumulation works on input lines, so it doesn't
* matter whether the query will be ignored due to \if.
*/
if (pset.cur_cmd_interactive && !line_saved_in_history)
{
......@@ -296,7 +313,9 @@ MainLoop(FILE *source)
line_saved_in_history = true;
}
/* execute query */
/* execute query unless we're in an inactive \if branch */
if (conditional_active(cond_stack))
{
success = SendQuery(query_buf->data);
slashCmdStatus = success ? PSQL_CMD_SEND : PSQL_CMD_ERROR;
pset.stmt_lineno = 1;
......@@ -313,6 +332,18 @@ MainLoop(FILE *source)
added_nl_pos = -1;
/* we need not do psql_scan_reset() here */
}
else
{
/* if interactive, warn about non-executed query */
if (pset.cur_cmd_interactive)
psql_error("query ignored; use \\endif or Ctrl-C to exit current \\if block\n");
/* fake an OK result for purposes of loop checks */
success = true;
slashCmdStatus = PSQL_CMD_SEND;
pset.stmt_lineno = 1;
/* note that query_buf doesn't change state */
}
}
else if (scan_result == PSCAN_BACKSLASH)
{
/* handle backslash command */
......@@ -343,21 +374,24 @@ MainLoop(FILE *source)
/* execute backslash command */
slashCmdStatus = HandleSlashCmds(scan_state,
query_buf->len > 0 ?
query_buf : previous_buf);
cond_stack,
query_buf,
previous_buf);
success = slashCmdStatus != PSQL_CMD_ERROR;
pset.stmt_lineno = 1;
if ((slashCmdStatus == PSQL_CMD_SEND || slashCmdStatus == PSQL_CMD_NEWEDIT) &&
query_buf->len == 0)
{
/* copy previous buffer to current for handling */
appendPQExpBufferStr(query_buf, previous_buf->data);
}
/*
* Resetting stmt_lineno after a backslash command isn't
* always appropriate, but it's what we've done historically
* and there have been few complaints.
*/
pset.stmt_lineno = 1;
if (slashCmdStatus == PSQL_CMD_SEND)
{
/* should not see this in inactive branch */
Assert(conditional_active(cond_stack));
success = SendQuery(query_buf->data);
/* transfer query to previous_buf by pointer-swapping */
......@@ -374,6 +408,8 @@ MainLoop(FILE *source)
}
else if (slashCmdStatus == PSQL_CMD_NEWEDIT)
{
/* should not see this in inactive branch */
Assert(conditional_active(cond_stack));
/* rescan query_buf as new input */
psql_scan_finish(scan_state);
free(line);
......@@ -429,8 +465,17 @@ MainLoop(FILE *source)
if (pset.cur_cmd_interactive)
pg_send_history(history_buf);
/* execute query */
/* execute query unless we're in an inactive \if branch */
if (conditional_active(cond_stack))
{
success = SendQuery(query_buf->data);
}
else
{
if (pset.cur_cmd_interactive)
psql_error("query ignored; use \\endif or Ctrl-C to exit current \\if block\n");
success = true;
}
if (!success && die_on_error)
successResult = EXIT_USER;
......@@ -438,6 +483,19 @@ MainLoop(FILE *source)
successResult = EXIT_BADCONN;
}
/*
* Check for unbalanced \if-\endifs unless user explicitly quit, or the
* script is erroring out
*/
if (slashCmdStatus != PSQL_CMD_TERMINATE &&
successResult != EXIT_USER &&
!conditional_stack_empty(cond_stack))
{
psql_error("reached EOF without finding closing \\endif(s)\n");
if (die_on_error && !pset.cur_cmd_interactive)
successResult = EXIT_USER;
}
/*
* Let's just make real sure the SIGINT handler won't try to use
* sigint_interrupt_jmp after we exit this routine. If there is an outer
......@@ -452,6 +510,7 @@ MainLoop(FILE *source)
destroyPQExpBuffer(history_buf);
psql_scan_destroy(scan_state);
conditional_stack_destroy(cond_stack);
pset.cur_cmd_source = prev_cmd_source;
pset.cur_cmd_interactive = prev_cmd_interactive;
......
......@@ -66,7 +66,7 @@
*/
char *
get_prompt(promptStatus_t status)
get_prompt(promptStatus_t status, ConditionalStack cstack)
{
#define MAX_PROMPT_SIZE 256
static char destination[MAX_PROMPT_SIZE + 1];
......@@ -188,7 +188,9 @@ get_prompt(promptStatus_t status)
switch (status)
{
case PROMPT_READY:
if (!pset.db)
if (cstack != NULL && !conditional_active(cstack))
buf[0] = '@';
else if (!pset.db)
buf[0] = '!';
else if (!pset.singleline)
buf[0] = '=';
......
......@@ -10,7 +10,8 @@
/* enum promptStatus_t is now defined by psqlscan.h */
#include "fe_utils/psqlscan.h"
#include "conditional.h"
char *get_prompt(promptStatus_t status);
char *get_prompt(promptStatus_t status, ConditionalStack cstack);
#endif /* PROMPT_H */
......@@ -18,8 +18,7 @@ enum slash_option_type
OT_SQLID, /* treat as SQL identifier */
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_NO_EVAL /* no expansion of backticks or variables */
OT_WHOLE_LINE /* just snarf the rest of the line */
};
......@@ -32,6 +31,10 @@ extern char *psql_scan_slash_option(PsqlScanState state,
extern void psql_scan_slash_command_end(PsqlScanState state);
extern int psql_scan_get_paren_depth(PsqlScanState state);
extern void psql_scan_set_paren_depth(PsqlScanState state, int depth);
extern void dequote_downcase_identifier(char *str, bool downcase, int encoding);
#endif /* PSQLSCANSLASH_H */
......@@ -19,6 +19,7 @@
#include "postgres_fe.h"
#include "psqlscanslash.h"
#include "conditional.h"
#include "libpq-fe.h"
}
......@@ -230,8 +231,7 @@ other .
:{variable_char}+ {
/* Possible psql variable substitution */
if (option_type == OT_NO_EVAL ||
cur_state->callbacks->get_variable == NULL)
if (cur_state->callbacks->get_variable == NULL)
ECHO;
else
{
......@@ -268,25 +268,15 @@ other .
}
:'{variable_char}+' {
if (option_type == OT_NO_EVAL)
ECHO;
else
{
psqlscan_escape_variable(cur_state, yytext, yyleng, false);
*option_quote = ':';
}
unquoted_option_chars = 0;
}
:\"{variable_char}+\" {
if (option_type == OT_NO_EVAL)
ECHO;
else
{
psqlscan_escape_variable(cur_state, yytext, yyleng, true);
*option_quote = ':';
}
unquoted_option_chars = 0;
}
......@@ -353,8 +343,9 @@ other .
*/
"`" {
/* In NO_EVAL mode, don't evaluate the command */
if (option_type != OT_NO_EVAL)
/* In an inactive \if branch, don't evaluate the command */
if (cur_state->cb_passthrough == NULL ||
conditional_active((ConditionalStack) cur_state->cb_passthrough))
evaluate_backtick(cur_state);
BEGIN(xslasharg);
}
......@@ -641,6 +632,25 @@ psql_scan_slash_command_end(PsqlScanState state)
psql_scan_reselect_sql_lexer(state);
}
/*
* Fetch current paren nesting depth
*/
int
psql_scan_get_paren_depth(PsqlScanState state)
{
return state->paren_depth;
}
/*
* Set paren nesting depth
*/
void
psql_scan_set_paren_depth(PsqlScanState state, int depth)
{
Assert(depth >= 0);
state->paren_depth = depth;
}
/*
* De-quote and optionally downcase a SQL identifier.
*
......
......@@ -331,6 +331,7 @@ main(int argc, char *argv[])
else if (cell->action == ACT_SINGLE_SLASH)
{
PsqlScanState scan_state;
ConditionalStack cond_stack;
if (pset.echo == PSQL_ECHO_ALL)
puts(cell->val);
......@@ -339,11 +340,17 @@ main(int argc, char *argv[])
psql_scan_setup(scan_state,
cell->val, strlen(cell->val),
pset.encoding, standard_strings());
cond_stack = conditional_stack_create();
psql_scan_set_passthrough(scan_state, (void *) cond_stack);
successResult = HandleSlashCmds(scan_state, NULL) != PSQL_CMD_ERROR
successResult = HandleSlashCmds(scan_state,
cond_stack,
NULL,
NULL) != PSQL_CMD_ERROR
? EXIT_SUCCESS : EXIT_FAILURE;
psql_scan_destroy(scan_state);
conditional_stack_destroy(cond_stack);
}
else if (cell->action == ACT_FILE)
{
......
......@@ -2735,6 +2735,175 @@ deallocate q;
\pset format aligned
\pset expanded off
\pset border 1
-- tests for \if ... \endif
\if true
select 'okay';
?column?
----------
okay
(1 row)
select 'still okay';
?column?
------------
still okay
(1 row)
\else
not okay;
still not okay
\endif
-- at this point query buffer should still have last valid line
\g
?column?
------------
still okay
(1 row)
-- \if should work okay on part of a query
select
\if true
42
\else
(bogus
\endif
forty_two;
forty_two
-----------
42
(1 row)
select \if false \\ (bogus \else \\ 42 \endif \\ forty_two;
forty_two
-----------
42
(1 row)
-- test a large nested if using a variety of true-equivalents
\if true
\if 1
\if yes
\if on
\echo 'all true'
all true
\else
\echo 'should not print #1-1'
\endif
\else
\echo 'should not print #1-2'
\endif
\else
\echo 'should not print #1-3'
\endif
\else
\echo 'should not print #1-4'
\endif
-- test a variety of false-equivalents in an if/elif/else structure
\if false
\echo 'should not print #2-1'
\elif 0
\echo 'should not print #2-2'
\elif no
\echo 'should not print #2-3'
\elif off
\echo 'should not print #2-4'
\else
\echo 'all false'
all false
\endif
-- test simple true-then-else
\if true
\echo 'first thing true'
first thing true
\else
\echo 'should not print #3-1'
\endif
-- test simple false-true-else
\if false
\echo 'should not print #4-1'
\elif true
\echo 'second thing true'
second thing true
\else
\echo 'should not print #5-1'
\endif
-- invalid boolean expressions are false
\if invalid boolean expression
unrecognized value "invalid boolean expression" for "\if expression": boolean expected
\echo 'will not print #6-1'
\else
\echo 'will print anyway #6-2'
will print anyway #6-2
\endif
-- test un-matched endif
\endif
\endif: no matching \if
-- test un-matched else
\else
\else: no matching \if
-- test un-matched elif
\elif
\elif: no matching \if
-- test double-else error
\if true
\else
\else
\else: cannot occur after \else
\endif
-- test elif out-of-order
\if false
\else
\elif
\elif: cannot occur after \else
\endif
-- test if-endif matching in a false branch
\if false
\if false
\echo 'should not print #7-1'
\else
\echo 'should not print #7-2'
\endif
\echo 'should not print #7-3'
\else
\echo 'should print #7-4'
should print #7-4
\endif
-- show that vars and backticks are not expanded when ignoring extra args
\set foo bar
\echo :foo :'foo' :"foo"
bar 'bar' "bar"
\pset fieldsep | `nosuchcommand` :foo :'foo' :"foo"
\pset: extra argument "nosuchcommand" ignored
\pset: extra argument ":foo" ignored
\pset: extra argument ":'foo'" ignored
\pset: extra argument ":"foo"" ignored
-- show that vars and backticks are not expanded and commands are ignored
-- when in a false if-branch
\set try_to_quit '\\q'
\if false
:try_to_quit
\echo `nosuchcommand` :foo :'foo' :"foo"
\pset fieldsep | `nosuchcommand` :foo :'foo' :"foo"
\a \C arg1 \c arg1 arg2 arg3 arg4 \cd arg1 \conninfo
\copy arg1 arg2 arg3 arg4 arg5 arg6
\copyright \dt arg1 \e arg1 arg2
\ef whole_line
\ev whole_line
\echo arg1 arg2 arg3 arg4 arg5 \echo arg1 \encoding arg1 \errverbose
\g arg1 \gx arg1 \gexec \h \html \i arg1 \ir arg1 \l arg1 \lo arg1 arg2
\o arg1 \p \password arg1 \prompt arg1 arg2 \pset arg1 arg2 \q
\reset \s arg1 \set arg1 arg2 arg3 arg4 arg5 arg6 arg7 \setenv arg1 arg2
\sf whole_line
\sv whole_line
\t arg1 \T arg1 \timing arg1 \unset arg1 \w arg1 \watch arg1 \x arg1
-- \else here is eaten as part of OT_FILEPIPE argument
\w |/no/such/file \else
-- \endif here is eaten as part of whole-line argument
\! whole_line \endif
\else
\echo 'should print #8-1'
should print #8-1
\endif
-- SHOW_CONTEXT
\set SHOW_CONTEXT never
do $$
......
......@@ -382,6 +382,150 @@ deallocate q;
\pset expanded off
\pset border 1
-- tests for \if ... \endif
\if true
select 'okay';
select 'still okay';
\else
not okay;
still not okay
\endif
-- at this point query buffer should still have last valid line
\g
-- \if should work okay on part of a query
select
\if true
42
\else
(bogus
\endif
forty_two;
select \if false \\ (bogus \else \\ 42 \endif \\ forty_two;
-- test a large nested if using a variety of true-equivalents
\if true
\if 1
\if yes
\if on
\echo 'all true'
\else
\echo 'should not print #1-1'
\endif
\else
\echo 'should not print #1-2'
\endif
\else
\echo 'should not print #1-3'
\endif
\else
\echo 'should not print #1-4'
\endif
-- test a variety of false-equivalents in an if/elif/else structure
\if false
\echo 'should not print #2-1'
\elif 0
\echo 'should not print #2-2'
\elif no
\echo 'should not print #2-3'
\elif off
\echo 'should not print #2-4'
\else
\echo 'all false'
\endif
-- test simple true-then-else
\if true
\echo 'first thing true'
\else
\echo 'should not print #3-1'
\endif
-- test simple false-true-else
\if false
\echo 'should not print #4-1'
\elif true
\echo 'second thing true'
\else
\echo 'should not print #5-1'
\endif
-- invalid boolean expressions are false
\if invalid boolean expression
\echo 'will not print #6-1'
\else
\echo 'will print anyway #6-2'
\endif
-- test un-matched endif
\endif
-- test un-matched else
\else
-- test un-matched elif
\elif
-- test double-else error
\if true
\else
\else
\endif
-- test elif out-of-order
\if false
\else
\elif
\endif
-- test if-endif matching in a false branch
\if false
\if false
\echo 'should not print #7-1'
\else
\echo 'should not print #7-2'
\endif
\echo 'should not print #7-3'
\else
\echo 'should print #7-4'
\endif
-- show that vars and backticks are not expanded when ignoring extra args
\set foo bar
\echo :foo :'foo' :"foo"
\pset fieldsep | `nosuchcommand` :foo :'foo' :"foo"
-- show that vars and backticks are not expanded and commands are ignored
-- when in a false if-branch
\set try_to_quit '\\q'
\if false
:try_to_quit
\echo `nosuchcommand` :foo :'foo' :"foo"
\pset fieldsep | `nosuchcommand` :foo :'foo' :"foo"
\a \C arg1 \c arg1 arg2 arg3 arg4 \cd arg1 \conninfo
\copy arg1 arg2 arg3 arg4 arg5 arg6
\copyright \dt arg1 \e arg1 arg2
\ef whole_line
\ev whole_line
\echo arg1 arg2 arg3 arg4 arg5 \echo arg1 \encoding arg1 \errverbose
\g arg1 \gx arg1 \gexec \h \html \i arg1 \ir arg1 \l arg1 \lo arg1 arg2
\o arg1 \p \password arg1 \prompt arg1 arg2 \pset arg1 arg2 \q
\reset \s arg1 \set arg1 arg2 arg3 arg4 arg5 arg6 arg7 \setenv arg1 arg2
\sf whole_line
\sv whole_line
\t arg1 \T arg1 \timing arg1 \unset arg1 \w arg1 \watch arg1 \x arg1
-- \else here is eaten as part of OT_FILEPIPE argument
\w |/no/such/file \else
-- \endif here is eaten as part of whole-line argument
\! whole_line \endif
\else
\echo 'should print #8-1'
\endif
-- SHOW_CONTEXT
\set SHOW_CONTEXT never
......
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