On Mon, Feb 6, 2017 at 2:32 PM, Fabien COELHO <coe...@cri.ensmp.fr> wrote:

>
> Do you think the TAP tests would benefit from having the input described
>> in a q(...) block rather than a string?
>>
>
> My 0.02€: Not really, so I would not bother. It breaks perl indentation
> and logic for a limited benefit. Maybe add comments if you feel that a test
> case is unclear.
>
> --
> Fabien.


Consolidated Fabien's TAP test additions with v7, in case anyone else wants
to be reviewing.
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..a703cab
--- /dev/null
+++ b/src/bin/psql/t/001_if.pl
@@ -0,0 +1,39 @@
+use strict;
+use warnings;
+
+use Config;
+use PostgresNode;
+use TestLib;
+use Test::More tests => 18;
+
+#
+# test invalid \if respects ON_ERROR_STOP
+#
+my $node = get_new_node('master');
+$node->init;
+$node->start;
+
+my $tests = [
+    # syntax errors
+       [ "\\if invalid\n\\echo NO\n\\endif\n\\echo NO\n", '', 'boolean 
expected', 'syntax error' ],
+       [ "\\if false\n\\elif invalid\n\\echo NO\n\\endif\n\\echo NO\n", '', 
'boolean expected', 'syntax error' ],
+       # unmatched checks
+       [ "\\if true\n", '', 'found EOF before closing.*endif', 'unmatched \if' 
],
+       [ "\\elif true\n\\echo NO\n", '', 'encountered un-matched.*elif', 
'unmatched \elif' ],
+       [ "\\else\n\\echo NO\n", '', 'encountered un-matched.*else', 'unmatched 
\else' ],
+       [ "\\endif\n\\echo NO\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 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