>
> Hmmm. ISTM that control-c must at least reset the stack, otherwise their
> is not easy way to get out. What can be left to another patch is doing a
> control-C for contents and then another one for the stack when there is no
> content.
>

And so it shall be.


> - put Fabien's tap test in place verbatim
>>
>
> Hmmm. That was really just a POC... I would suggest some more tests, eg:
>
>   # elif error
>   "\\if false\n\\elif error\n\\endif\n"
>
>   # ignore commands on error (stdout must be empty)
>   "\\if error\n\\echo NO\n\\else\n\\echo NO\n\\endif\n"
>

Those are already in the regression (around line 2763 of
expected/psql.out), are you saying we should have them in TAP as well?
Should we only do TAP tests?

Anyway, here's the Ctrl-C behavior:

# \if true
new \if is true, executing commands
# \echo msg
msg
# ^C
escaped \if, executing commands
# \if false
new \if is false, ignoring commands until next \elif, \else, or \endif
# \echo msg
inside inactive branch, command ignored.
# ^C
escaped \if, executing commands
# \echo msg
msg
# \endif
encountered un-matched \endif
#


Ctrl-C exits do the same before/after state checks that \endif does, the
lone difference being that it "escaped" the \if rather than "exited" the
\if. Thanks to Daniel for pointing out where it should be handled, because
I wasn't going to figure that out on my own.

v7's only major difference from v6 is the Ctrl-C branch escaping.
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index ae58708..c0ba4c4 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -2035,6 +2035,67 @@ hello 10
         </listitem>
       </varlistentry>
 
+      <varlistentry>
+        <term><literal>\if</literal> <replaceable 
class="parameter">expr</replaceable></term>
+        <term><literal>\elif</literal> <replaceable 
class="parameter">expr</replaceable></term>
+        <term><literal>\else</literal></term>
+        <term><literal>\endif</literal></term>
+        <listitem>
+        <para>
+        This group of commands implements nestable conditional blocks, like
+        this:
+        </para>
+<programlisting>
+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 should never print'
+    \endif
+\endif
+</programlisting>
+        <para>
+        Conditional blocks must begin with a <command>\if</command> and end
+        with an <command>\endif</command>, and the pairs must be found in
+        the same source file. If an EOF is reached on the main file or an
+        <command>\include</command>-ed file before all local
+        <command>\if</command>-<command>\endif</command> are matched, then
+        psql will raise an error.
+        </para>
+        <para>
+        A conditional block can have any number of 
+        <command>\elif</command> clauses, which may optionally be followed by a
+        single <command>\else</command> clause.
+        </para>
+        <para>
+        The <command>\if</command> and <command>\elif</command> commands
+        read the rest of the line and evaluate it as a boolean expression.
+        Currently, expressions are limited to a single unquoted string
+        which are evaluated like other on/off options, so the valid values
+        are any unambiguous case insensitive matches 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>.  So 
+        <literal>t</literal>, <literal>T</literal>, and <literal>tR</literal>
+        will all match <literal>true</literal>.
+        </para>
+        <para>
+        Lines within false branches are not evaluated in any way: queries are
+        not sent to the server, non-conditional commands are not evaluated but
+        bluntly ignored, nested if-expressions in such branches are also not
+        evaluated but are tallied to check for proper nesting.
+        </para>
+        </listitem>
+      </varlistentry>
 
       <varlistentry>
         <term><literal>\ir</literal> or <literal>\include_relative</literal> 
<replaceable class="parameter">filename</replaceable></term>
diff --git a/src/bin/psql/Makefile b/src/bin/psql/Makefile
index c53733f..7a418c6 100644
--- a/src/bin/psql/Makefile
+++ b/src/bin/psql/Makefile
@@ -61,8 +61,16 @@ uninstall:
 
 clean distclean:
        rm -f psql$(X) $(OBJS) lex.backup
+       rm -rf tmp_check
 
 # files removed here are supposed to be in the distribution tarball,
 # so do not clean them in the clean/distclean rules
 maintainer-clean: distclean
        rm -f sql_help.h sql_help.c psqlscanslash.c
+
+
+check:
+       $(prove_check)
+
+installcheck:
+       $(prove_installcheck)
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index f17f610..4a3e471 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -49,6 +49,7 @@
 #include "psqlscanslash.h"
 #include "settings.h"
 #include "variables.h"
+#include "fe_utils/psqlscan_int.h"
 
 /*
  * Editable database object types.
@@ -132,7 +133,7 @@ HandleSlashCmds(PsqlScanState scan_state,
                status = PSQL_CMD_ERROR;
        }
 
-       if (status != PSQL_CMD_ERROR)
+       if (status != PSQL_CMD_ERROR && pset.active_branch)
        {
                /* eat any remaining arguments after a valid command */
                /* note we suppress evaluation of backticks here */
@@ -194,6 +195,30 @@ read_connect_arg(PsqlScanState scan_state)
        return result;
 }
 
+/*
+ * Read and interpret argument as a boolean expression.
+ * Return true if a boolean value was successfully parsed.
+ */
+static bool
+read_boolean_expression(PsqlScanState scan_state, char *action,
+                                               bool *result)
+{
+       char    *value = psql_scan_slash_option(scan_state,
+                                                                               
        OT_NORMAL, NULL, false);
+       bool    success = ParseVariableBool(value, action, result);
+       free(value);
+       return success;
+}
+
+/*
+ * 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);
+}
 
 /*
  * Subroutine to actually try to execute a backslash command.
@@ -207,6 +232,17 @@ exec_command(const char *cmd,
                                                                 * failed */
        backslashResult status = PSQL_CMD_SKIP_LINE;
 
+       if (!pset.active_branch && !is_branching_command(cmd))
+       {
+               if (pset.cur_cmd_interactive)
+                       psql_error("inside inactive branch, command 
ignored.\n");
+
+               /* Continue with an empty buffer as if the command were never 
read */
+               resetPQExpBuffer(query_buf);
+               psql_scan_reset(scan_state);
+               return status;
+       }
+
        /*
         * \a -- toggle field alignment This makes little sense but we keep it
         * around.
@@ -1006,6 +1042,251 @@ exec_command(const char *cmd,
                }
        }
 
+       /*
+        * \if <expr> is the beginning of an \if..\endif block.
+        * <expr> must be a valid boolean expression, or the whole block will be
+        * ignored.
+        * If this \if is itself a part of a branch that is false/ignored, it 
too
+        * will automatically be ignored.
+        */
+       else if (strcmp(cmd, "if") == 0)
+       {
+               ifState new_if_state = IFSTATE_IGNORED;
+               /*
+                * only evaluate the expression for truth if the underlying
+                * branch is active
+                */
+               if (pset.active_branch)
+               {
+                       bool if_true = false;
+                       success = read_boolean_expression(scan_state, "\\if 
<expr>", &if_true);
+                       if (success)
+                       {
+                               if (if_true)
+                               {
+                                       new_if_state = IFSTATE_TRUE;
+                                       if (pset.cur_cmd_interactive)
+                                               psql_error("new \\if is true, 
executing commands\n");
+                                       pset.active_branch = true;
+                               }
+                               else
+                               {
+                                       new_if_state = IFSTATE_FALSE;
+                                       if (pset.cur_cmd_interactive)
+                                               psql_error("new \\if is false, 
ignoring commands "
+                                                                       "until 
next \\elif, \\else, or \\endif\n");
+                                       pset.active_branch = false;
+                               }
+                       }
+                       else
+                       {
+                               /*
+                                * show this error in both interactive and 
script, because the
+                                * session is now in a state where all blocks 
will be ignored
+                                * regardless of expression truth
+                                */
+                               psql_error("new \\if is invalid, "
+                                                       "ignoring commands 
until next \\endif\n");
+                               pset.active_branch = false;
+                       }
+               }
+               else
+               {
+                       if (pset.cur_cmd_interactive)
+                               psql_error("new \\if is inside ignored block, "
+                                               "ignoring commands until next 
\\endif\n");
+                       pset.active_branch = false;
+               }
+
+               psqlscan_branch_push(scan_state,new_if_state);
+               psql_scan_reset(scan_state);
+       }
+
+       /*
+        * \elif <expr> is part of an \if..\endif block
+        * <expr> will only be evalated for boolean truth if no previous
+        * \if or \endif in the block has evaluated to true and the \if..\endif
+        * block is not itself being ignored.
+        * in the event that <expr> does not conform to a proper boolean 
expression,
+        * all following statements in the block will be ignored until \endif is
+        * encountered.
+        *
+        */
+       else if (strcmp(cmd, "elif") == 0)
+       {
+               bool elif_true = false;
+               switch (psqlscan_branch_get_state(scan_state))
+               {
+                       case IFSTATE_IGNORED:
+                               /*
+                                * inactive branch, do nothing:
+                                * either if-endif already had a true block,
+                                * or whole parent block is false.
+                                */
+                               if (pset.cur_cmd_interactive)
+                                       psql_error("\\elif is inside ignored 
block, "
+                                                       "ignoring commands 
until next \\endif\n");
+                               pset.active_branch = false;
+                               break;
+                       case IFSTATE_TRUE:
+                               /*
+                                * just finished true section of active branch
+                                * do not evaluate expression, just skip
+                                */
+                               if (pset.cur_cmd_interactive)
+                                       psql_error("\\elif is after true block, 
"
+                                                       "ignoring commands 
until next \\endif\n");
+                               psqlscan_branch_set_state(scan_state, 
IFSTATE_IGNORED);
+                               pset.active_branch = false;
+                               break;
+                       case IFSTATE_FALSE:
+                               /*
+                                * have not yet found a true block in this 
if-endif,
+                                * determine if this section is true or not.
+                                * variable expansion must be temporarily 
turned back
+                                * on to read the boolean expression.
+                                */
+                               pset.active_branch = true;
+                               success = read_boolean_expression(scan_state, 
"\\elif <expr>",
+                                                                               
                        &elif_true);
+                               if (success)
+                               {
+                                       if (elif_true)
+                                       {
+                                               
psqlscan_branch_set_state(scan_state, IFSTATE_TRUE);
+                                               if (pset.cur_cmd_interactive)
+                                                       psql_error("\\elif is 
true, executing commands\n");
+                                               pset.active_branch = false;
+                                       }
+                                       else
+                                       {
+                                               if (pset.cur_cmd_interactive)
+                                                       psql_error("\\elif is 
false, ignoring commands "
+                                                                       "until 
next \\elif, \\else, or \\endif\n");
+                                               pset.active_branch = false;
+                                       }
+                               }
+                               else
+                               {
+                                       /*
+                                        * show this error in both interactive 
and script, because the
+                                        * session is now in a state where all 
blocks will be ignored
+                                        * regardless of expression truth
+                                        */
+                                       psql_error("\\elif is invalid, "
+                                                               "ignoring 
commands until next \\endif\n");
+                                       psqlscan_branch_set_state(scan_state, 
IFSTATE_IGNORED);
+                                       pset.active_branch = false;
+                               }
+                               pset.active_branch = 
psqlscan_branch_active(scan_state);
+                               break;
+                       case IFSTATE_ELSE_TRUE:
+                       case IFSTATE_ELSE_FALSE:
+                               psql_error("encountered \\elif after \\else\n");
+                               success = false;
+                               pset.active_branch = false;
+                               break;
+                       case IFSTATE_NONE:
+                               /* no if to elif from */
+                               psql_error("encountered un-matched \\elif\n");
+                               success = false;
+                               break;
+                       default:
+                               break;
+               }
+               psql_scan_reset(scan_state);
+       }
+
+       /*
+        * \else is part of an \if..\endif block
+        * the statements within an \else branch will only be executed if
+        * all previous \if and \endif expressions evaluated to false
+        * and the block was not itself being ignored.
+        */
+       else if (strcmp(cmd, "else") == 0)
+       {
+               switch (psqlscan_branch_get_state(scan_state))
+               {
+                       case IFSTATE_TRUE:
+                       case IFSTATE_IGNORED:
+                               /*
+                                * either just finished true section of an 
active branch,
+                                * or whole branch was inactive. either way, be 
on the
+                                * lookout for any invalid \endif or \else 
commands
+                                */
+                               if (pset.cur_cmd_interactive)
+                                       psql_error("\\else after true condition 
or in ignored block, "
+                                                               "ignoring 
commands until next \\endif\n");
+                               pset.active_branch = false;
+                               psqlscan_branch_set_state(scan_state, 
IFSTATE_ELSE_FALSE);
+                               break;
+                       case IFSTATE_FALSE:
+                               /* just finished false section of an active 
branch */
+                               if (pset.cur_cmd_interactive)
+                                       psql_error("\\else after all previous 
conditions false, "
+                                                               "executing 
commands\n");
+                               psqlscan_branch_set_state(scan_state, 
IFSTATE_ELSE_TRUE);
+                               pset.active_branch = true;
+                               break;
+                       case IFSTATE_NONE:
+                               /* no if to else from */
+                               psql_error("encountered un-matched \\else\n");
+                               success = false;
+                               break;
+                       case IFSTATE_ELSE_TRUE:
+                       case IFSTATE_ELSE_FALSE:
+                               psql_error("encountered \\else after \\else\n");
+                               success = false;
+                               pset.active_branch = false;
+                               break;
+                       default:
+                               break;
+               }
+               psql_scan_reset(scan_state);
+       }
+
+       /*
+        * \endif - closing statment of an \if...\endif block
+        */
+       else if (strcmp(cmd, "endif") == 0)
+       {
+               /*
+                * get rid of this ifstate element and look at the previous
+                * one, if any
+                */
+               if (psqlscan_branch_pop(scan_state))
+               {
+                       bool was_active = pset.active_branch;
+                       pset.active_branch = psqlscan_branch_active(scan_state);
+
+                       if (pset.cur_cmd_interactive)
+                       {
+                               if (psqlscan_branch_stack_empty(scan_state))
+                                       /* no more branches */
+                                       psql_error("exited \\if, executing 
commands\n");
+                               else if (!pset.active_branch)
+                                       /* was ignoring, still ignoring */
+                                       psql_error("exited \\if to false parent 
branch, "
+                                                       "ignoring commands 
until next \\endif\n");
+                               else if (was_active)
+                                       /* was true, still true */
+                                       psql_error("exited \\if to true parent 
branch, "
+                                                       "continuing executing 
commands\n");
+                               else
+                                       /* was false, is true again */
+                                       psql_error("exited \\if to true parent 
branch, "
+                                                       "resuming executing 
commands\n");
+                       }
+               }
+               else
+               {
+                       psql_error("encountered un-matched \\endif\n");
+                       success = false;
+               }
+
+               psql_scan_reset(scan_state);
+       }
+
        /* \l is list databases */
        else if (strcmp(cmd, "l") == 0 || strcmp(cmd, "list") == 0 ||
                         strcmp(cmd, "l+") == 0 || strcmp(cmd, "list+") == 0)
diff --git a/src/bin/psql/common.c b/src/bin/psql/common.c
index 6e3acdc..9f1a072 100644
--- a/src/bin/psql/common.c
+++ b/src/bin/psql/common.c
@@ -119,6 +119,11 @@ setQFout(const char *fname)
  * If "escape" is true, return the value suitably quoted and escaped,
  * as an identifier or string literal depending on "as_ident".
  * (Failure in escaping should lead to returning NULL.)
+ *
+ * Variables are not expanded if the current branch is inactive
+ * (part of an \if..\endif section which is false). \elif branches
+ * will need temporarily mark the branch active in order to
+ * properly evaluate conditionals.
  */
 char *
 psql_get_variable(const char *varname, bool escape, bool as_ident)
@@ -126,6 +131,10 @@ psql_get_variable(const char *varname, bool escape, bool 
as_ident)
        char       *result;
        const char *value;
 
+       /* do not expand variables if the branch is inactive */
+       if (!pset.active_branch)
+               return NULL;
+
        value = GetVariable(pset.vars, varname);
        if (!value)
                return NULL;
diff --git a/src/bin/psql/help.c b/src/bin/psql/help.c
index 3e3cab4..9f9e1a6 100644
--- a/src/bin/psql/help.c
+++ b/src/bin/psql/help.c
@@ -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, _("Conditionals\n"));
+       fprintf(output, _("  \\if <expr>             begin a conditional 
block\n"));
+       fprintf(output, _("  \\elif <expr>           else if in the current 
conditional block\n"));
+       fprintf(output, _("  \\else                  else in the current 
conditional block\n"));
+       fprintf(output, _("  \\endif                 end current 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"));
diff --git a/src/bin/psql/mainloop.c b/src/bin/psql/mainloop.c
index 6e358e2..099cf8c 100644
--- a/src/bin/psql/mainloop.c
+++ b/src/bin/psql/mainloop.c
@@ -15,7 +15,7 @@
 #include "settings.h"
 
 #include "mb/pg_wchar.h"
-
+#include "fe_utils/psqlscan_int.h"
 
 /* callback functions for our flex lexer */
 const PsqlScanCallbacks psqlscan_callbacks = {
@@ -23,6 +23,23 @@ const PsqlScanCallbacks psqlscan_callbacks = {
        psql_error
 };
 
+/*
+ * execute query if branch is active.
+ * warn interactive users about ignored queries.
+ */
+static
+bool send_query(const char *query)
+{
+       /* execute query if branch is active */
+       if (pset.active_branch)
+               return SendQuery(query);
+
+       if (pset.cur_cmd_interactive)
+               psql_error("inside inactive branch, query ignored. "
+                                       "use \\endif to exit current 
branch.\n");
+
+       return true;
+}
 
 /*
  * Main processing loop for reading lines of input
@@ -122,7 +139,35 @@ MainLoop(FILE *source)
                        cancel_pressed = false;
 
                        if (pset.cur_cmd_interactive)
+                       {
                                putc('\n', stdout);
+                               /*
+                                * if interactive user is in a branch, then 
Ctrl-C will exit
+                                * from the inner-most branch
+                                */
+                               if (!psqlscan_branch_stack_empty(scan_state))
+                               {
+                                       bool was_active = pset.active_branch;
+                                       psqlscan_branch_pop(scan_state);
+                                       pset.active_branch = 
psqlscan_branch_active(scan_state);
+
+                                       if 
(psqlscan_branch_stack_empty(scan_state))
+                                               /* no more branches */
+                                               psql_error("escaped \\if, 
executing commands\n");
+                                       else if (!pset.active_branch)
+                                               /* was ignoring, still ignoring 
*/
+                                               psql_error("escaped \\if to 
false parent branch, "
+                                                               "ignoring 
commands until next \\endif\n");
+                                       else if (was_active)
+                                               /* was true, still true */
+                                               psql_error("escaped \\if to 
true parent branch, "
+                                                               "continuing 
executing commands\n");
+                                       else
+                                               /* was false, is true again */
+                                               psql_error("escaped \\if to 
true parent branch, "
+                                                               "resuming 
executing commands\n");
+                               }
+                       }
                        else
                        {
                                successResult = EXIT_USER;
@@ -296,8 +341,8 @@ MainLoop(FILE *source)
                                        line_saved_in_history = true;
                                }
 
-                               /* execute query */
-                               success = SendQuery(query_buf->data);
+                               success = send_query(query_buf->data);
+
                                slashCmdStatus = success ? PSQL_CMD_SEND : 
PSQL_CMD_ERROR;
                                pset.stmt_lineno = 1;
 
@@ -358,7 +403,7 @@ MainLoop(FILE *source)
 
                                if (slashCmdStatus == PSQL_CMD_SEND)
                                {
-                                       success = SendQuery(query_buf->data);
+                                       success = send_query(query_buf->data);
 
                                        /* transfer query to previous_buf by 
pointer-swapping */
                                        {
@@ -367,6 +412,7 @@ MainLoop(FILE *source)
                                                previous_buf = query_buf;
                                                query_buf = swap_buf;
                                        }
+
                                        resetPQExpBuffer(query_buf);
 
                                        /* flush any paren nesting info after 
forced send */
@@ -429,8 +475,7 @@ MainLoop(FILE *source)
                if (pset.cur_cmd_interactive)
                        pg_send_history(history_buf);
 
-               /* execute query */
-               success = SendQuery(query_buf->data);
+               success = send_query(query_buf->data);
 
                if (!success && die_on_error)
                        successResult = EXIT_USER;
@@ -451,6 +496,15 @@ MainLoop(FILE *source)
        destroyPQExpBuffer(previous_buf);
        destroyPQExpBuffer(history_buf);
 
+       /* check for unbalanced \if-\endifs unless user explicitly quit */
+       if (slashCmdStatus != PSQL_CMD_TERMINATE
+               && !psqlscan_branch_stack_empty(scan_state))
+       {
+               psql_error("found EOF before closing \\endif(s)\n");
+               if (die_on_error)
+                       successResult = EXIT_USER;
+       }
+
        psql_scan_destroy(scan_state);
 
        pset.cur_cmd_source = prev_cmd_source;
diff --git a/src/bin/psql/settings.h b/src/bin/psql/settings.h
index 195f5a1..c8aeab6 100644
--- a/src/bin/psql/settings.h
+++ b/src/bin/psql/settings.h
@@ -114,6 +114,9 @@ typedef struct _psqlSettings
 
        VariableSpace vars;                     /* "shell variable" repository 
*/
 
+       bool            active_branch;  /* true if session is not in an \if 
branch
+                                                                * or the 
current branch is true */
+
        /*
         * The remaining fields are set by assign hooks associated with entries 
in
         * "vars".  They should not be set directly except by those hook
diff --git a/src/bin/psql/startup.c b/src/bin/psql/startup.c
index 88d686a..5c8760a 100644
--- a/src/bin/psql/startup.c
+++ b/src/bin/psql/startup.c
@@ -128,6 +128,8 @@ main(int argc, char *argv[])
        setvbuf(stderr, NULL, _IONBF, 0);
 #endif
 
+       pset.active_branch = true;
+
        pset.progname = get_progname(argv[0]);
 
        pset.db = NULL;
diff --git a/src/bin/psql/t/001_if.pl b/src/bin/psql/t/001_if.pl
new file mode 100644
index 0000000..68c9b15
--- /dev/null
+++ b/src/bin/psql/t/001_if.pl
@@ -0,0 +1,37 @@
+use strict;
+use warnings;
+
+use Config;
+use PostgresNode;
+use TestLib;
+use Test::More tests => 15;
+
+#
+# test invalid \if respects ON_ERROR_STOP
+#
+my $node = get_new_node('master');
+$node->init;
+$node->start;
+
+my $tests = [
+       [ "\\if invalid_expression\n\\endif\n", '', 'boolean expected', 'syntax 
error' ],
+       # unmatched checks
+       [ "\\if true\\n", '', 'found EOF before closing.*endif', 'unmatched 
\if' ],
+       [ "\\elif true\\n", '', 'encountered un-matched.*elif', 'unmatched 
\elif' ],
+       [ "\\else\\n", '', 'encountered un-matched.*else', 'unmatched \else' ],
+       [ "\\endif\\n", '', 'encountered un-matched.*endif', 'unmatched \endif' 
],
+];
+
+# 3 checks per tests
+for my $test (@$tests) {
+  my ($script, $stdout_expect, $stderr_re, $name) = @$test;
+  my ($stdout, $stderr);
+  my $retcode = $node->psql('postgres', $script,
+               stdout => \$stdout, stderr => \$stderr,
+               on_error_stop => 1);
+  is($retcode,'3',"$name test respects ON_ERROR_STOP");
+  is($stdout, $stdout_expect, "$name test STDOUT");
+  like($stderr, qr/$stderr_re/, "$name test STDERR");
+}
+
+$node->teardown_node;
diff --git a/src/fe_utils/psqlscan.l b/src/fe_utils/psqlscan.l
index 1b29341..998630a 100644
--- a/src/fe_utils/psqlscan.l
+++ b/src/fe_utils/psqlscan.l
@@ -904,6 +904,8 @@ psql_scan_create(const PsqlScanCallbacks *callbacks)
 
        psql_scan_reset(state);
 
+       state->branch_stack = NULL;
+
        return state;
 }
 
@@ -919,6 +921,13 @@ psql_scan_destroy(PsqlScanState state)
 
        yylex_destroy(state->scanner);
 
+       while (state->branch_stack != NULL)
+       {
+               IfStackElem *p = state->branch_stack;
+               state->branch_stack = state->branch_stack->next;
+               free(p);
+       }
+
        free(state);
 }
 
@@ -1426,3 +1435,86 @@ psqlscan_escape_variable(PsqlScanState state, const char 
*txt, int len,
                psqlscan_emit(state, txt, len);
        }
 }
+
+/*
+ * psqlscan_branch_stack_empty
+ *
+ * True if there are no active \if-structures
+ */
+bool
+psqlscan_branch_stack_empty(PsqlScanState state)
+{
+       return state->branch_stack == NULL;
+}
+
+/*
+ * Fetch the current state of the top of the stack
+ */
+ifState
+psqlscan_branch_get_state(PsqlScanState state)
+{
+       if (psqlscan_branch_stack_empty(state))
+               return IFSTATE_NONE;
+       return state->branch_stack->if_state;
+}
+
+/*
+ * psqlscan_branch_active
+ *
+ * True if the current \if-block (if any) is true and queries/commands
+ * should be executed.
+ */
+bool
+psqlscan_branch_active(PsqlScanState state)
+{
+       ifState s = psqlscan_branch_get_state(state);
+       return s == IFSTATE_NONE || s == IFSTATE_TRUE || s == IFSTATE_ELSE_TRUE;
+}
+
+
+/*
+ * psqlscan_branch_push
+ *
+ * Create a new \if branch.
+ */
+bool
+psqlscan_branch_push(PsqlScanState state, ifState new_state)
+{
+       IfStackElem *p = pg_malloc0(sizeof(IfStackElem));
+       p->if_state = new_state;
+       p->next = state->branch_stack;
+       state->branch_stack = p;
+       return true;
+}
+
+/*
+ * psqlscan_branch_set_state
+ *
+ * Change the state of the topmost branch.
+ * Returns false if there was branch state to set.
+ */
+bool
+psqlscan_branch_set_state(PsqlScanState state, ifState new_state)
+{
+       if (psqlscan_branch_stack_empty(state))
+               return false;
+       state->branch_stack->if_state = new_state;
+       return true;
+}
+
+/*
+ * psqlscan_branch_pop
+ *
+ * Destroy the topmost branch because and \endif was encountered.
+ * Returns false if there was no branch to end.
+ */
+bool
+psqlscan_branch_pop(PsqlScanState state)
+{
+       IfStackElem *p = state->branch_stack;
+       if (!p)
+               return false;
+       state->branch_stack = state->branch_stack->next;
+       free(p);
+       return true;
+}
diff --git a/src/include/fe_utils/psqlscan_int.h 
b/src/include/fe_utils/psqlscan_int.h
index 0fddc7a..321d455 100644
--- a/src/include/fe_utils/psqlscan_int.h
+++ b/src/include/fe_utils/psqlscan_int.h
@@ -75,6 +75,30 @@ typedef struct StackElem
        struct StackElem *next;
 } StackElem;
 
+typedef enum _ifState
+{
+       IFSTATE_NONE = 0,       /* Not currently in an \if block */
+       IFSTATE_TRUE,           /* currently in an \if or \elif which is true
+                                                * and all parent branches (if 
any) are true */
+       IFSTATE_FALSE,          /* currently in an \if or \elif which is false
+                                                * but no true branch has yet 
been seen,
+                                                * and all parent branches (if 
any) are true */
+       IFSTATE_IGNORED,        /* currently in an \elif which follows a true 
\if
+                                                * or the whole \if is a child 
of a false parent */
+       IFSTATE_ELSE_TRUE,      /* currently in an \else which is true
+                                                * and all parent branches (if 
any) are true */
+       IFSTATE_ELSE_FALSE      /* currently in an \else which is false or 
ignored */
+} ifState;
+
+/*
+ * The state of nested ifs is stored in a stack.
+ */
+typedef struct IfStackElem
+{
+       ifState         if_state;
+       struct IfStackElem *next;
+} IfStackElem;
+
 /*
  * All working state of the lexer must be stored in PsqlScanStateData
  * between calls.  This allows us to have multiple open lexer operations,
@@ -118,6 +142,11 @@ typedef struct PsqlScanStateData
         * Callback functions provided by the program making use of the lexer.
         */
        const PsqlScanCallbacks *callbacks;
+
+       /*
+        * \if branch state variables
+        */
+       IfStackElem *branch_stack;
 } PsqlScanStateData;
 
 
@@ -141,4 +170,21 @@ extern void psqlscan_escape_variable(PsqlScanState state,
                                                 const char *txt, int len,
                                                 bool as_ident);
 
+/*
+ * branching commands
+ */
+extern bool psqlscan_branch_stack_empty(PsqlScanState state);
+
+extern bool psqlscan_branch_active(PsqlScanState state);
+
+extern ifState psqlscan_branch_get_state(PsqlScanState state);
+
+extern bool psqlscan_branch_push(PsqlScanState state,
+                                                                       ifState 
new_state);
+
+extern bool psqlscan_branch_set_state(PsqlScanState state,
+                                                                       ifState 
new_state);
+
+extern bool psqlscan_branch_pop(PsqlScanState state);
+
 #endif   /* PSQLSCAN_INT_H */
diff --git a/src/test/regress/expected/psql.out 
b/src/test/regress/expected/psql.out
index 026a4f0..d4f1849 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -2712,6 +2712,96 @@ deallocate q;
 \pset format aligned
 \pset expanded off
 \pset border 1
+-- 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
+-- test invalidation of whole if-endif when invalid boolean is given
+\if invalid_boolean_expression
+unrecognized value "invalid_boolean_expression" for "\if <expr>": boolean 
expected
+new \if is invalid, ignoring commands until next \endif
+       \echo 'should not print #6-1'
+\else
+       \echo 'should not print because whole if-then is wrecked #6-2'
+\endif
+-- test non-evaluation of variables in false blocks
+\set var 'ab''cd'
+-- select :var;
+\if false
+  select :var ;
+\else
+  select 1;
+ ?column? 
+----------
+        1
+(1 row)
+
+\endif
+-- test un-matched endif
+\endif
+encountered un-matched \endif
+-- test un-matched else
+\else
+encountered un-matched \else
+-- test un-matched elif
+\elif
+encountered un-matched \elif
+-- test double-else error
+\if true
+\else
+\else
+encountered \else after \else
+\endif
+-- test elif out-of-order
+\if false
+\else
+\elif
+encountered \elif after \else
+\endif
 -- SHOW_CONTEXT
 \set SHOW_CONTEXT never
 do $$
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index d823d11..ba41d9e 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -375,6 +375,91 @@ deallocate q;
 \pset expanded off
 \pset border 1
 
+-- 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
+
+-- test invalidation of whole if-endif when invalid boolean is given
+\if invalid_boolean_expression
+       \echo 'should not print #6-1'
+\else
+       \echo 'should not print because whole if-then is wrecked #6-2'
+\endif
+
+-- test non-evaluation of variables in false blocks
+\set var 'ab''cd'
+-- select :var;
+\if false
+  select :var ;
+\else
+  select 1;
+\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
+
 -- SHOW_CONTEXT
 
 \set SHOW_CONTEXT never
-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Reply via email to