]> granicus.if.org Git - postgresql/commitdiff
Support \if ... \elif ... \else ... \endif in psql scripting.
authorTom Lane <tgl@sss.pgh.pa.us>
Thu, 30 Mar 2017 16:59:11 +0000 (12:59 -0400)
committerTom Lane <tgl@sss.pgh.pa.us>
Thu, 30 Mar 2017 16:59:24 +0000 (12:59 -0400)
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

17 files changed:
doc/src/sgml/ref/psql-ref.sgml
src/bin/psql/Makefile
src/bin/psql/command.c
src/bin/psql/command.h
src/bin/psql/common.c
src/bin/psql/conditional.c [new file with mode: 0644]
src/bin/psql/conditional.h [new file with mode: 0644]
src/bin/psql/copy.c
src/bin/psql/help.c
src/bin/psql/mainloop.c
src/bin/psql/prompt.c
src/bin/psql/prompt.h
src/bin/psql/psqlscanslash.h
src/bin/psql/psqlscanslash.l
src/bin/psql/startup.c
src/test/regress/expected/psql.out
src/test/regress/sql/psql.sql

index 2a9c4120205a0c4633a52fa5d81354110a28369f..b51b11baa35022a4b9914d043c137f4c8ad1961b 100644 (file)
@@ -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
index f8e31eacbe152bbad502e8b7c104e4fdacdde80e..ab2cfa6353c46e8302e0ed82af26c9592d49c4c8 100644 (file)
@@ -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)
 
 
index 4f4a0aa9bd48e85cc5a6535ee1dc5173cb0096a8..94a3cfce9071b57d3a20182f5392e83b5577303d 100644 (file)
@@ -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,
-                        PQExpBuffer query_buf);
-static bool do_edit(const char *filename_arg, PQExpBuffer query_buf,
-               int lineno, bool *edited);
+                        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 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)))
                {
-                       psql_error("\\%s: extra argument \"%s\" ignored\n", cmd, arg);
+                       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);
 
-       /*
-        * \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.
-        */
-       else if (strcmp(cmd, "c") == 0 || strcmp(cmd, "connect") == 0)
+       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 port "prt" of current host
+ *                                     as user "usr".
+ * \c dbs                      Connect to database "dbs" on current port of current host
+ *                                     as current user.
+ */
+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
-        */
-       else if (strcmp(cmd, "e") == 0 || strcmp(cmd, "edit") == 0)
+       return status;
+}
+
+/*
+ * \e or \edit -- edit the current query buffer, or edit a file and
+ * make it the query buffer
+ */
+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);
 
-       /*
-        * \ef -- edit the named function, or present a blank CREATE FUNCTION
-        * template if no argument is given
-        */
-       else if (strcmp(cmd, "ef") == 0)
+       return status;
+}
+
+/*
+ * \ef -- edit the named function, or present a blank CREATE FUNCTION
+ * template if no argument is given
+ */
+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
-        */
-       else if (strcmp(cmd, "ev") == 0)
+       return status;
+}
+
+/*
+ * \ev -- edit the named view, or present a blank CREATE VIEW
+ * template if no argument is given
+ */
+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,9 +1202,19 @@ 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)
+       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);
@@ -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);
 
-       /*
-        * \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)
+       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
+ */
+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
-        */
-       else if (strncmp(cmd, "lo_", 3) == 0)
+       return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR;
+}
+
+/*
+ * \lo_* -- large object operations
+ */
+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);
                }
+
+               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 command */
-       else if (strcmp(cmd, "setenv") == 0)
+/*
+ * \setenv -- set environment variable
+ */
+static backslashResult
+exec_command_setenv(PsqlScanState scan_state, bool active_branch,
+                                       const char *cmd)
+{
+       bool            success = true;
+
+       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);
 
-#if 0
+       return PSQL_CMD_SKIP_LINE;
+}
+
+
+/*
+ * 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;
-               char       *value;
+       result = psql_scan_slash_option(scan_state, OT_SQLIDHACK, &quote, true);
 
-               while ((value = psql_scan_slash_option(scan_state,
-                                                                                          OT_NORMAL, NULL, true)))
-               {
-                       psql_error("+ opt(%d) = |%s|\n", i++, value);
-                       free(value);
-               }
+       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, false)) != NULL)
+       {
+               /* 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);
 }
 
 /*
index d0c32645f14e886bf2cfbdb70fcca142331e3290..e8ea8473e848e84713919a8e02e9b64650885cce 100644 (file)
@@ -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);
 
index e9d4fe678669a088796f3d7480ef897320669c74..b06ae9779d57843cf73f06b05df8617ee3fdca6c 100644 (file)
@@ -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;
diff --git a/src/bin/psql/conditional.c b/src/bin/psql/conditional.c
new file mode 100644 (file)
index 0000000..63977ce
--- /dev/null
@@ -0,0 +1,153 @@
+/*
+ * 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;
+}
diff --git a/src/bin/psql/conditional.h b/src/bin/psql/conditional.h
new file mode 100644 (file)
index 0000000..90e4d93
--- /dev/null
@@ -0,0 +1,83 @@
+/*
+ * 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 */
index 481031a22955ee53fbe8f95db8e887e6f6ed8cf6..2005b9a0bfcb0e36604790164424c569ce6a891c 100644 (file)
@@ -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);
index ba14df0344df735eb17283fc0877ce7b58d2cec5..ac435220e62d3e3a590aab753780810ebc1d167c 100644 (file)
@@ -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"));
index 6e358e2e1b8470d69b83126864f31d7eea2295c8..2bc2f43b4e7c67956600e13e7e4b56f468fab880 100644 (file)
@@ -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,22 +313,36 @@ MainLoop(FILE *source)
                                        line_saved_in_history = true;
                                }
 
-                               /* execute query */
-                               success = SendQuery(query_buf->data);
-                               slashCmdStatus = success ? PSQL_CMD_SEND : PSQL_CMD_ERROR;
-                               pset.stmt_lineno = 1;
-
-                               /* transfer query to previous_buf by pointer-swapping */
+                               /* execute query unless we're in an inactive \if branch */
+                               if (conditional_active(cond_stack))
                                {
-                                       PQExpBuffer swap_buf = previous_buf;
+                                       success = SendQuery(query_buf->data);
+                                       slashCmdStatus = success ? PSQL_CMD_SEND : PSQL_CMD_ERROR;
+                                       pset.stmt_lineno = 1;
 
-                                       previous_buf = query_buf;
-                                       query_buf = swap_buf;
-                               }
-                               resetPQExpBuffer(query_buf);
+                                       /* transfer query to previous_buf by pointer-swapping */
+                                       {
+                                               PQExpBuffer swap_buf = previous_buf;
 
-                               added_nl_pos = -1;
-                               /* we need not do psql_scan_reset() here */
+                                               previous_buf = query_buf;
+                                               query_buf = swap_buf;
+                                       }
+                                       resetPQExpBuffer(query_buf);
+
+                                       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)
                        {
@@ -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 */
-               success = SendQuery(query_buf->data);
+               /* 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;
index f7930c4a83991a8f16a0b5e0a1df9ce4e2574452..e502ff3f6e61292a8726a95959eae56c1d302675 100644 (file)
@@ -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] = '=';
index 977e754293c1f4ca5469daa07c7fac6dea8b3392..b3d2d98fd7df03ec7dcb6f692258fcb9cc17f83e 100644 (file)
@@ -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 */
index 266e93af44047008d8dc55cda91b8b33f7bf4980..db76061332e7486097e1aba359a39d983b1dec22 100644 (file)
@@ -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 */
index ba4a08d00051f9fa16152007d49c046e965c8866..319afdc744165fc6199016b93470812f3452ccb8 100644 (file)
@@ -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 = ':';
-                                       }
+                                       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 = ':';
-                                       }
+                                       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.
  *
index 694f0ef257fe1b431d81fbd1333e3a125b557f7c..8068a28b4e2142c22c69370f1517eef8b6e6b623 100644 (file)
@@ -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)
                        {
index eb7f197b122040c99683f231c97196ec92780127..8aa914fa95764a627c644a8fab3a5e4f1feaaf0b 100644 (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 $$
index 8f8e17a87cd94537eaeba9860203077eb0df6bcb..0ae4dd84eab7dc902754c6e5878830e91d0bb088 100644 (file)
@@ -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