On Fri, Feb 3, 2017 at 12:57 AM, Fabien COELHO <coe...@cri.ensmp.fr> wrote:

>
> Hello Corey,
>
> Glad you like the barking. I'm happy to let the prompt issue rest for now,
>> we can always add it later.
>>
>> If we DID want it, however, I don't think it'll be hard to add a special
>> prompt (Thinking %T or %Y because they both look like branches, heh),
>>
>
> Ah!:-) T may stand for Tree, but Y looks a little bit more like branches.
> Maybe Y for Yew.
>

Well played. The %Y prompt can be a separate patch.

New patch. Highlights:
- rebased to master as of ten minutes ago
- Interactive barking on branching state changes, commands typed while in
inactive state
- Help text. New block in help text called "Conditionals"
- SendQuery calls in mainloop.c are all encapsulated in send_query() to
ensure the same if-active and if-interactive logic is used
- Exactly one perl TAP test, testing ON_ERROR_STOP. I predict more will be
needed, but I'm not sure what coverage is desired
- I also predict that my TAP test style is pathetic
- regression tests now have comments to explain purpose
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..dcf567e 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,176 @@ 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)
+                               new_if_state = (if_true) ? IFSTATE_TRUE : 
IFSTATE_FALSE;
+               }
+               psqlscan_branch_push(scan_state,new_if_state);
+               pset.active_branch = psqlscan_branch_active(scan_state);
+               psql_scan_reset(scan_state);
+               if (pset.cur_cmd_interactive && success)
+                       psql_error("entered if: %s, %s commands\n",
+                                               (pset.active_branch) ? "active" 
: "inactive",
+                                               (pset.active_branch) ? 
"executing" : "ignoring");
+       }
+
+       /*
+        * \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)
+       {
+               if (psqlscan_branch_stack_empty(scan_state))
+               {
+                       psql_error("encountered un-matched \\elif\n");
+                       success = false;
+               }
+               else
+               {
+                       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.
+                                        */
+                                       break;
+                               case IFSTATE_TRUE:
+                                       /*
+                                        * just finished true section of active 
branch
+                                        * do not evaluate expression, just skip
+                                        */
+                                       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);
+                                       pset.active_branch = false;
+                                       if (!success)
+                                               
psqlscan_branch_set_state(scan_state, IFSTATE_IGNORED);
+                                       else if (elif_true)
+                                               
psqlscan_branch_set_state(scan_state, IFSTATE_TRUE);
+                                       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;
+                                       break;
+                               default:
+                                       break;
+                       }
+               }
+               psql_scan_reset(scan_state);
+               if (pset.cur_cmd_interactive && success)
+                       psql_error("entered elif: %s, %s commands\n",
+                                               (pset.active_branch) ? "active" 
: "inactive",
+                                               (pset.active_branch) ? 
"executing" : "ignoring");
+       }
+
+       /*
+        * \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)
+       {
+               if (psqlscan_branch_stack_empty(scan_state))
+               {
+                       psql_error("encountered un-matched \\else\n");
+                       success = false;
+               }
+               else
+               {
+                       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
+                                        */
+                                       psqlscan_branch_set_state(scan_state, 
IFSTATE_ELSE_FALSE);
+                                       break;
+                               case IFSTATE_FALSE:
+                                       /* just finished false section of an 
active branch */
+                                       psqlscan_branch_set_state(scan_state, 
IFSTATE_ELSE_TRUE);
+                                       pset.active_branch = true;
+                                       break;
+                               case IFSTATE_ELSE_TRUE:
+                               case IFSTATE_ELSE_FALSE:
+                                       psql_error("encountered \\else after 
\\else\n");
+                                       success = false;
+                                       break;
+                               default:
+                                       break;
+                       }
+               }
+               pset.active_branch = psqlscan_branch_active(scan_state);
+               psql_scan_reset(scan_state);
+               if (pset.cur_cmd_interactive && success)
+                       psql_error("entered else: %s, %s commands\n",
+                                               (pset.active_branch) ? "active" 
: "inactive",
+                                               (pset.active_branch) ? 
"executing" : "ignoring");
+       }
+
+       /*
+        * \endif - closing statment of an \if...\endif block
+        */
+       else if (strcmp(cmd, "endif") == 0)
+       {
+               if (psqlscan_branch_stack_empty(scan_state))
+               {
+                       psql_error("encountered un-matched \\endif\n");
+                       success = false;
+               }
+               else
+               {
+                       psqlscan_branch_pop(scan_state);
+                       pset.active_branch = psqlscan_branch_active(scan_state);
+               }
+               psql_scan_reset(scan_state);
+               if (pset.cur_cmd_interactive && success)
+                       psql_error("exited if: %s, %s commands\n",
+                                               (pset.active_branch) ? "active" 
: "inactive",
+                                               (pset.active_branch) ? 
"executing" : "ignoring");
+       }
+
        /* \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..543b0d5 100644
--- a/src/bin/psql/help.c
+++ b/src/bin/psql/help.c
@@ -307,6 +307,21 @@ slashUsage(unsigned short int pager)
                                          "  \\lo_list\n"
                                          "  \\lo_unlink LOBOID      large 
object operations\n"));
 
+       fprintf(output, _("Conditionals\n"));
+       fprintf(output, _("  \\if <expr>             begin a conditional block, 
and execute subsequent\n"
+                                         "                          
commands/queires if <expr> is true, otherwise\n"
+                                         "                          skip them 
without evaluation\n"));
+       fprintf(output, _("  \\elif <expr>           within a conditional 
block, exectue subsequennt\n"
+                                         "                          
commands/queires if <expr> is true and all\n"
+                                         "                          previous 
\\if and \\elif expressions were false\n"));
+       fprintf(output, _("  \\elif <expr>           within a conditional 
block, exectue subsequennt\n"
+                                         "                          
commands/queires only if all previous \\if and\n"
+                                         "                          \\elif 
expressions were false\n"));
+       fprintf(output, _("  \\endif                 end a conditional block, 
restoring\n"
+                                         "                          
executing/skipping state to what it was before\n"
+                                         "                          the 
matching \\if command.\n"));
+       fprintf(output, "\n");
+
        ClosePager(output);
 }
 
diff --git a/src/bin/psql/mainloop.c b/src/bin/psql/mainloop.c
index 6e358e2..ed0330d 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,22 @@ 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.\n");
+
+       return true;
+}
 
 /*
  * Main processing loop for reading lines of input
@@ -296,8 +312,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 +374,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 +383,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 +446,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 +467,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..2ed01c5
--- /dev/null
+++ b/src/bin/psql/t/001_if.pl
@@ -0,0 +1,32 @@
+use strict;
+use warnings;
+
+use Config;
+use PostgresNode;
+use TestLib;
+use Test::More tests => 3;
+
+#
+# test invalid \if respects ON_ERROR_STOP
+#
+my $stdout;
+my $stderr;
+my $node = get_new_node('master');
+$node->init;
+$node->start;
+
+my $script_1 = q(\if invalid_expression
+);
+
+my $stderr_1  = q(psql:<stdin>:1: unrecognized value "invalid_expression" for 
"\if <expr>": boolean expected
+psql:<stdin>:1: found EOF before closing \endif(s));
+
+my $retcode = $node->psql('postgres', $script_1,
+               stdout => \$stdout, stderr => \$stderr,
+               on_error_stop => 1);
+is($retcode,'3','Invalid \if respects ON_ERROR_STOP');
+is($stdout,'','STDOUT mismatch');
+is($stderr, $stderr_1, 'STDERR mismatch');
+
+
+$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..d615eb8 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -2712,6 +2712,95 @@ 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
+       \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