Attached is the latest work. Not everything is done yet. I post it because
the next step is likely to be "tedious" as Tom put it, and if there's a way
out of it, I want to avoid it.

What is done:
- all changes here built off the v22 patch
- any function which had scan_state and cond_stack passed in now only has
scan_state, and cond_stack is extracted from the cb_passthrough pointer.
- ConditonalStack is now only explictly passed to get_prompt ... which
doesn't have scan state
- Conditional commands no longer reset scan state, nor do they clear the
query buffer
- boolean expressions consume all options, but only evaluate variables and
backticks in situations where those would be active
- invalid boolean arguments are treated as false
- contextually wrong \else, \endif, \elif are still errors

What is not done:
- TAP tests are not converted to regular regression test(s)
- skipped slash commands still consume the rest of the line

That last part is big, to quote Tom:

* More generally, I do not think that the approach of having exec_command
simply fall out immediately when in a false branch is going to work,
because it ignores the fact that different backslash commands have
different argument parsing rules.  Some will eat the rest of the line and
some won't.  I'm afraid that it might be necessary to remove that code
block and add a test to every single backslash command that decides
whether to actually perform its action after it's consumed its arguments.
That would be tedious :-(.  But as it stands, backslash commands will get
parsed differently (ie with potentially-different ending points) depending
on whether they're in a live branch or not, and that seems just way too
error-prone to be allowed to stand.


If that's what needs to be done, does it make sense to first commit a
pre-patch that encapsulates each command family ( \c and \connect are a
family,  all \d* commands are one family) into its own static function? It
would make the follow-up patch to if-endif cleaner and easier to review.


On Thu, Mar 16, 2017 at 5:14 PM, Tom Lane <t...@sss.pgh.pa.us> wrote:

> Corey Huinker <corey.huin...@gmail.com> writes:
> > Ok, I've got some time now and I'm starting to dig into this. I'd like to
> > restate what I *think* my feedback is, in case I missed or misunderstood
> > something.
> > ...
> > 3. Change command scans to scan the whole boolean expression, not just
> > OT_NORMAL.
> > There's a couple ways to go about this. My gut reaction is to create a
> new
> > scan type OT_BOOL_EXPR, which for the time being is the same as
> > OT_WHOLE_LINE, but could one day be something different.
>
> OT_WHOLE_LINE is not what you want because that results in verbatim
> copying, without variable expansion or anything.  My vote would be to
> repeatedly do OT_NORMAL until you get a NULL, thereby consuming as
> many regular arguments as the backslash command has.  (After which,
> if it wasn't exactly one argument, complain, for the moment.  But this
> leaves the door open for something like "\if :foo = :bar".)  Note that
> this implies that "\if some-expression \someothercommand" will be allowed,
> but I think that's fine, as I see no reason to allow backslashes in
> whatever if-expression syntax we invent later.  OT_WHOLE_LINE is a bit of
> a bastard child and I'd just as soon not define it as being the lexing
> behavior of any new commands.
>
> > 5. Allow contextually-correct invalid boolean expressions to map to
> false.
>
> > Out-of-context \endif, \else, and \elif commands remain as errors to be
> > ignored, invalid expressions in an \if or legallyl-placed \elif are just
> > treated as false.
>
> WFM.
>
>                         regards, tom lane
>
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 2a9c412..7743fb0 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -2064,6 +2064,102 @@ hello 10
 
 
       <varlistentry>
+        <term><literal>\if</literal> <replaceable 
class="parameter">expression</replaceable></term>
+        <term><literal>\elif</literal> <replaceable 
class="parameter">expression</replaceable></term>
+        <term><literal>\else</literal></term>
+        <term><literal>\endif</literal></term>
+        <listitem>
+        <para>
+        This group of commands implements nestable conditional blocks.
+        A conditional block must begin with an <command>\if</command> and end
+        with an <command>\endif</command>.  In between there may be any number
+        of <command>\elif</command> clauses, which may optionally be followed
+        by a single <command>\else</command> clause.  Ordinary queries and
+        other types of backslash commands may (and usually do) appear between
+        the commands forming a conditional block.
+        </para>
+        <para>
+        The <command>\if</command> and <command>\elif</command> commands read
+        the rest of the line and evaluate it as a boolean expression.  If the
+        expression is <literal>true</literal> then processing continues
+        normally; otherwise, lines are skipped until a
+        matching <command>\elif</command>, <command>\else</command>,
+        or <command>\endif</command> is reached.  Once
+        an <command>\if</command> or <command>\elif</command> has succeeded,
+        later matching <command>\elif</command> commands are not evaluated but
+        are treated as false.  Lines following an <command>\else</command> are
+        processed only if no earlier matching <command>\if</command>
+        or <command>\elif</command> succeeded.
+        </para>
+        <para>
+        Lines being skipped are parsed normally to identify queries and
+        backslash commands, but queries are not sent to the server, and
+        backslash commands other than conditionals
+        (<command>\if</command>, <command>\elif</command>,
+        <command>\else</command>, <command>\endif</command>) are
+        ignored.  Conditional commands are checked only for valid nesting.
+        </para>
+        <para>
+        The <replaceable class="parameter">expression</replaceable> argument
+        of <command>\if</command> or <command>\elif</command>
+        is subject to variable interpolation and backquote expansion, just
+        like any other backslash command argument.  After that it is evaluated
+        like the value of an on/off option variable.  So a valid value
+        is any unambiguous case-insensitive match for one of:
+        <literal>true</literal>, <literal>false</literal>, 
<literal>1</literal>,
+        <literal>0</literal>, <literal>on</literal>, <literal>off</literal>,
+        <literal>yes</literal>, <literal>no</literal>.  For example,
+        <literal>t</literal>, <literal>T</literal>, and <literal>tR</literal>
+        will all be considered to be <literal>true</literal>.
+        </para>
+        <para>
+        Expressions that do not properly evaluate to true or false will
+        generate an error and cause the <command>\if</command> or
+        <command>\elif</command> command to fail.  Because that behavior may
+        change branching context in undesirable ways (executing code which
+        was intended to be skipped, causing <command>\elif</command>,
+        <command>\else</command>, and <command>\endif</command> commands to
+        pair with the wrong <command>\if</command>, etc), it is
+        recommended that scripts that use conditionals also set
+        <varname>ON_ERROR_STOP</varname>.
+        </para>
+        <para>
+        All the backslash commands of a given conditional block must appear in
+        the same source file. If EOF is reached on the main input file or an
+        <command>\include</command>-ed file before all local
+        <command>\if</command>-blocks have been closed,
+        then <application>psql</> will raise an error.
+        </para>
+        <para>
+         Here is an example:
+        </para>
+<programlisting>
+-- set ON_ERROR_STOP in case the variables are not valid boolean expressions
+\set ON_ERROR_STOP on
+-- check for the existence of two separate records in the database and store
+-- the results in separate psql variables
+SELECT
+    EXISTS(SELECT 1 FROM customer WHERE customer_id = 123) as is_customer,
+    EXISTS(SELECT 1 FROM employee WHERE employee_id = 456) as is_employee
+\gset
+\if :is_customer
+    SELECT * FROM customer WHERE customer_id = 123;
+\elif :is_employee
+    \echo 'is not a customer but is an employee'
+    SELECT * FROM employee WHERE employee_id = 456;
+\else
+    \if yes
+        \echo 'not a customer or employee'
+    \else
+        \echo 'this will never print'
+    \endif
+\endif
+</programlisting>
+        </listitem>
+      </varlistentry>
+
+
+      <varlistentry>
         <term><literal>\l[+]</literal> or <literal>\list[+] [ <link 
linkend="APP-PSQL-patterns"><replaceable 
class="parameter">pattern</replaceable></link> ]</literal></term>
         <listitem>
         <para>
@@ -3715,7 +3811,8 @@ testdb=&gt; <userinput>INSERT INTO my_table VALUES 
(:'content');</userinput>
         <listitem>
         <para>
         In prompt 1 normally <literal>=</literal>,
-        but <literal>^</literal> if in single-line mode,
+        but <literal>@</literal> if the session is in a false conditional
+        block, or <literal>^</literal> if in single-line mode,
         or <literal>!</literal> if the session is disconnected from the
         database (which can happen if <command>\connect</command> fails).
         In prompt 2 <literal>%R</literal> is replaced by a character that
diff --git a/src/bin/psql/.gitignore b/src/bin/psql/.gitignore
index c2862b1..5239013 100644
--- a/src/bin/psql/.gitignore
+++ b/src/bin/psql/.gitignore
@@ -3,3 +3,5 @@
 /sql_help.c
 
 /psql
+
+/tmp_check/
diff --git a/src/bin/psql/Makefile b/src/bin/psql/Makefile
index f8e31ea..90ee85d 100644
--- a/src/bin/psql/Makefile
+++ b/src/bin/psql/Makefile
@@ -21,10 +21,10 @@ REFDOCDIR= $(top_srcdir)/doc/src/sgml/ref
 override CPPFLAGS := -I. -I$(srcdir) -I$(libpq_srcdir) $(CPPFLAGS)
 LDFLAGS += -L$(top_builddir)/src/fe_utils -lpgfeutils -lpq
 
-OBJS=  command.o common.o help.o input.o stringutils.o mainloop.o copy.o \
+OBJS=  command.o common.o conditional.o copy.o help.o input.o mainloop.o \
        startup.o prompt.o variables.o large_obj.o describe.o \
        crosstabview.o tab-complete.o \
-       sql_help.o psqlscanslash.o \
+       sql_help.o stringutils.o psqlscanslash.o \
        $(WIN32RES)
 
 
@@ -57,8 +57,15 @@ 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 4f4a0aa..4bdac08 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -35,6 +35,7 @@
 #include "fe_utils/string_utils.h"
 
 #include "common.h"
+#include "conditional.h"
 #include "copy.h"
 #include "crosstabview.h"
 #include "describe.h"
@@ -42,6 +43,7 @@
 #include "input.h"
 #include "large_obj.h"
 #include "mainloop.h"
+#include "fe_utils/psqlscan_int.h"
 #include "fe_utils/print.h"
 #include "psqlscanslash.h"
 #include "settings.h"
@@ -85,6 +87,12 @@ static void checkWin32Codepage(void);
 #endif
 
 
+static ConditionalStack
+get_conditional_stack(PsqlScanState scan_state)
+{
+       return (ConditionalStack) scan_state->cb_passthrough;
+}
+
 
 /*----------
  * HandleSlashCmds:
@@ -111,8 +119,10 @@ HandleSlashCmds(PsqlScanState scan_state,
        backslashResult status = PSQL_CMD_SKIP_LINE;
        char       *cmd;
        char       *arg;
+       ConditionalStack cstack = get_conditional_stack(scan_state);
 
        Assert(scan_state != NULL);
+       Assert(cstack != NULL);
 
        /* Parse off the command name */
        cmd = psql_scan_slash_command(scan_state);
@@ -129,7 +139,7 @@ HandleSlashCmds(PsqlScanState scan_state,
                status = PSQL_CMD_ERROR;
        }
 
-       if (status != PSQL_CMD_ERROR)
+       if (status != PSQL_CMD_ERROR && conditional_active(cstack))
        {
                /* eat any remaining arguments after a valid command */
                /* note we suppress evaluation of backticks here */
@@ -191,6 +201,94 @@ read_connect_arg(PsqlScanState scan_state)
        return result;
 }
 
+/*
+ * Read a boolean expression.
+ * Expand variables/backticks if expansion is true.
+ * Issue warning for nonstandard number of options is warn is true.
+ */
+static char*
+gather_boolean_expression(PsqlScanState scan_state, bool expansion, bool warn)
+{
+       enum slash_option_type slash_opt = (expansion) ? OT_NORMAL : OT_NO_EVAL;
+       char    *expression_buffer = NULL;
+       int             num_options = 0;
+       char    *value;
+
+       /* digest all options for the conditional command */
+       while ((value = psql_scan_slash_option(scan_state, slash_opt, NULL, 
false)))
+       {
+               num_options++;
+
+               if (expression_buffer)
+               {
+                       char *old_expr_buf = expression_buffer;
+                       expression_buffer = psprintf("%s %s", old_expr_buf, 
value);
+                       free(old_expr_buf);
+                       free(value);
+               }
+               else
+                       expression_buffer = value;
+       }
+
+       /* currently, we expect exactly one option */
+       if (warn)
+       {
+               if (num_options == 0)
+                       psql_error("WARNING: Boolean expression with no 
options");
+               else if (num_options > 1)
+                       psql_error("WARNING: Boolean expression with %d 
options: %s\n",
+                                               num_options, expression_buffer);
+       }
+
+       return expression_buffer;
+}
+
+/*
+ * Read a boolean expression, but do nothing with it.
+ */
+static void
+ignore_boolean_expression(PsqlScanState scan_state)
+{
+       free(gather_boolean_expression(scan_state, false, false));
+}
+
+/*
+ * Read and interpret argument as a boolean expression.
+ * Return true if a boolean value was successfully parsed.
+ * Do not clobber result if parse was not successful.
+ */
+static bool
+read_boolean_expression(PsqlScanState scan_state, char *action,
+                                               bool expansion, bool *result)
+{
+       char    *expr = gather_boolean_expression(scan_state, true, true);
+       bool    success = ParseVariableBool(expr, action, result);
+       free(expr);
+       return success;
+}
+
+/*
+ * Read a boolean expression, return true if the expression
+ * was a valid boolean expression that evaluated to true.
+ * Otherwise return false.
+ */
+static bool
+is_true_boolean_expression(PsqlScanState scan_state, char *action)
+{
+       bool tf;
+       bool success = read_boolean_expression(scan_state, action, true, &tf);
+       return (success) ? tf : false;
+}
+
+/*
+ * 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.
@@ -200,10 +298,27 @@ exec_command(const char *cmd,
                         PsqlScanState scan_state,
                         PQExpBuffer query_buf)
 {
+       ConditionalStack cstack = get_conditional_stack(scan_state);
        bool            success = true; /* indicate here if the command ran ok 
or
                                                                 * failed */
        backslashResult status = PSQL_CMD_SKIP_LINE;
 
+       if (!conditional_active(cstack) && !is_branching_command(cmd))
+       {
+               char    *arg;
+
+               if (pset.cur_cmd_interactive)
+                       psql_error("command ignored, use \\endif or Ctrl-C to 
exit "
+                                               "current branch.\n");
+
+               /* Digest and ignore any options on this command */
+               while ((arg = psql_scan_slash_option(scan_state,
+                                                                               
         OT_NO_EVAL, NULL, false)))
+                       free(arg);
+
+               return status;
+       }
+
        /*
         * \a -- toggle field alignment This makes little sense but we keep it
         * around.
@@ -1008,6 +1123,143 @@ exec_command(const char *cmd,
                }
        }
 
+       /*
+        * \if <expr> is the beginning of an \if..\endif block. <expr> must be a
+        * valid boolean expression, or the command will be ignored. If this \if
+        * is itself a part of a branch that is false/ignored, the expression
+        * will be checked for validity but cannot override the outer block.
+        */
+       else if (strcmp(cmd, "if") == 0)
+       {
+               switch (conditional_stack_peek(cstack))
+               {
+                       case IFSTATE_IGNORED:
+                       case IFSTATE_FALSE:
+                       case IFSTATE_ELSE_FALSE:
+                               /* new if-block, expression should not be 
evaluated,
+                                * but call it in silent mode to digest options 
*/
+                               ignore_boolean_expression(scan_state);
+                               conditional_stack_push(cstack, IFSTATE_IGNORED);
+                               break;
+                       default:
+                               /*
+                                * new if-block, check expression for truth.
+                                */
+                               if (is_true_boolean_expression(scan_state, 
"\\if <expr>"))
+                                       conditional_stack_push(cstack, 
IFSTATE_TRUE);
+                               else
+                                       conditional_stack_push(cstack, 
IFSTATE_FALSE);
+                               break;
+               }
+       }
+
+       /*
+        * \elif <expr> is part of an \if..\endif block. <expr> must be a valid
+        * boolean expression, or the command will be ignored.
+        */
+       else if (strcmp(cmd, "elif") == 0)
+       {
+               switch (conditional_stack_peek(cstack))
+               {
+                       case IFSTATE_IGNORED:
+                               /*
+                                * inactive branch, digest expression and move 
on.
+                                * either if-endif already had a true block,
+                                * or whole parent block is false.
+                                */
+                               ignore_boolean_expression(scan_state);
+                               break;
+                       case IFSTATE_TRUE:
+                               /*
+                                * just finished true section of this if-endif, 
digest
+                                * expression, but then ignore the rest until 
\endif
+                                */
+                               ignore_boolean_expression(scan_state);
+                               conditional_stack_poke(cstack, IFSTATE_IGNORED);
+                               break;
+                       case IFSTATE_FALSE:
+                               /*
+                                * have not yet found a true block in this 
if-endif,
+                                * this might be the first.
+                                */
+                               if (is_true_boolean_expression(scan_state, 
"\\elif <expr>"))
+                                       conditional_stack_poke(cstack, 
IFSTATE_TRUE);
+                               break;
+                       case IFSTATE_NONE:
+                               /* no if to elif from */
+                               psql_error("\\elif: no matching \\if\n");
+                               success = false;
+                               break;
+                       case IFSTATE_ELSE_TRUE:
+                       case IFSTATE_ELSE_FALSE:
+                               psql_error("\\elif: cannot occur after 
\\else\n");
+                               success = false;
+                               break;
+                       default:
+                               break;
+               }
+       }
+
+       /*
+        * \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 (conditional_stack_peek(cstack))
+               {
+                       case IFSTATE_FALSE:
+                               /* just finished false section of an active 
branch */
+                               conditional_stack_poke(cstack, 
IFSTATE_ELSE_TRUE);
+                               break;
+                       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
+                                */
+                               conditional_stack_poke(cstack, 
IFSTATE_ELSE_FALSE);
+                               break;
+                       case IFSTATE_NONE:
+                               /* no if to else from */
+                               psql_error("\\else: no matching \\if\n");
+                               success = false;
+                               break;
+                       case IFSTATE_ELSE_TRUE:
+                       case IFSTATE_ELSE_FALSE:
+                               psql_error("\\else: cannot occur after 
\\else\n");
+                               success = false;
+                               break;
+                       default:
+                               break;
+               }
+       }
+
+       /*
+        * \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
+                */
+               switch (conditional_stack_peek(cstack))
+               {
+                       case IFSTATE_NONE:
+                               psql_error("\\endif: no matching \\if\n");
+                               success = false;
+                               break;
+                       default:
+                               success = conditional_stack_pop(cstack);
+                               Assert(success);
+                               break;
+               }
+       }
+
        /* \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 e9d4fe6..e2299f4 100644
--- a/src/bin/psql/common.c
+++ b/src/bin/psql/common.c
@@ -24,6 +24,7 @@
 
 #include "settings.h"
 #include "command.h"
+#include "conditional.h"
 #include "copy.h"
 #include "crosstabview.h"
 #include "fe_utils/mbprint.h"
@@ -121,7 +122,8 @@ setQFout(const char *fname)
  * (Failure in escaping should lead to returning NULL.)
  *
  * "passthrough" is the pointer previously given to psql_scan_set_passthrough.
- * psql currently doesn't use this.
+ * Currently, passthrough points to a ConditionalStack, but in the future
+ * it may point to a structure with additional state information.
  */
 char *
 psql_get_variable(const char *varname, bool escape, bool as_ident,
diff --git a/src/bin/psql/copy.c b/src/bin/psql/copy.c
index 481031a..2005b9a 100644
--- a/src/bin/psql/copy.c
+++ b/src/bin/psql/copy.c
@@ -552,7 +552,7 @@ handleCopyIn(PGconn *conn, FILE *copystream, bool isbinary, 
PGresult **res)
                /* interactive input probably silly, but give one prompt anyway 
*/
                if (showprompt)
                {
-                       const char *prompt = get_prompt(PROMPT_COPY);
+                       const char *prompt = get_prompt(PROMPT_COPY, NULL);
 
                        fputs(prompt, stdout);
                        fflush(stdout);
@@ -590,7 +590,7 @@ handleCopyIn(PGconn *conn, FILE *copystream, bool isbinary, 
PGresult **res)
 
                        if (showprompt)
                        {
-                               const char *prompt = get_prompt(PROMPT_COPY);
+                               const char *prompt = get_prompt(PROMPT_COPY, 
NULL);
 
                                fputs(prompt, stdout);
                                fflush(stdout);
diff --git a/src/bin/psql/help.c b/src/bin/psql/help.c
index ba14df0..79afafb 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..e9e1e3f 100644
--- a/src/bin/psql/mainloop.c
+++ b/src/bin/psql/mainloop.c
@@ -25,6 +25,23 @@ const PsqlScanCallbacks psqlscan_callbacks = {
 
 
 /*
+ * execute query if branch is active.
+ * warn interactive users about ignored queries.
+ */
+static bool
+send_query(const char *query, ConditionalStack cstack)
+{
+       /* execute query if branch is active */
+       if (conditional_active(cstack))
+               return SendQuery(query);
+
+       if (pset.cur_cmd_interactive)
+               psql_error("query ignored; use \\endif or Ctrl-C to exit 
current \\if branch\n");
+
+       return true;
+}
+
+/*
  * Main processing loop for reading lines of input
  *     and sending them to the backend.
  *
@@ -35,6 +52,7 @@ int
 MainLoop(FILE *source)
 {
        PsqlScanState scan_state;       /* lexer working state */
+       ConditionalStack cond_stack;    /* \if status stack */
        volatile PQExpBuffer query_buf;         /* buffer for query being 
accumulated */
        volatile PQExpBuffer previous_buf;      /* if there isn't anything in 
the new
                                                                                
 * buffer yet, use this one for \e,
@@ -69,6 +87,8 @@ MainLoop(FILE *source)
 
        /* Create working state */
        scan_state = psql_scan_create(&psqlscan_callbacks);
+       cond_stack = conditional_stack_create();
+       psql_scan_set_passthrough(scan_state, (void *) cond_stack);
 
        query_buf = createPQExpBuffer();
        previous_buf = createPQExpBuffer();
@@ -122,7 +142,18 @@ 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 (!conditional_stack_empty(cond_stack))
+                               {
+                                       psql_error("\\if: escaped\n");
+                                       conditional_stack_pop(cond_stack);
+                               }
+                       }
                        else
                        {
                                successResult = EXIT_USER;
@@ -140,7 +171,8 @@ MainLoop(FILE *source)
                        /* May need to reset prompt, eg after \r command */
                        if (query_buf->len == 0)
                                prompt_status = PROMPT_READY;
-                       line = gets_interactive(get_prompt(prompt_status), 
query_buf);
+                       line = gets_interactive(get_prompt(prompt_status, 
cond_stack),
+                                                                       
query_buf);
                }
                else
                {
@@ -297,7 +329,7 @@ MainLoop(FILE *source)
                                }
 
                                /* execute query */
-                               success = SendQuery(query_buf->data);
+                               success = send_query(query_buf->data, 
cond_stack);
                                slashCmdStatus = success ? PSQL_CMD_SEND : 
PSQL_CMD_ERROR;
                                pset.stmt_lineno = 1;
 
@@ -358,7 +390,7 @@ MainLoop(FILE *source)
 
                                if (slashCmdStatus == PSQL_CMD_SEND)
                                {
-                                       success = SendQuery(query_buf->data);
+                                       success = send_query(query_buf->data, 
cond_stack);
 
                                        /* transfer query to previous_buf by 
pointer-swapping */
                                        {
@@ -430,7 +462,7 @@ MainLoop(FILE *source)
                        pg_send_history(history_buf);
 
                /* execute query */
-               success = SendQuery(query_buf->data);
+               success = send_query(query_buf->data, cond_stack);
 
                if (!success && die_on_error)
                        successResult = EXIT_USER;
@@ -439,6 +471,19 @@ MainLoop(FILE *source)
        }
 
        /*
+        * Check for unbalanced \if-\endifs unless user explicitly quit, or the
+        * script is erroring out
+        */
+       if (slashCmdStatus != PSQL_CMD_TERMINATE &&
+               successResult != EXIT_USER &&
+               !conditional_stack_empty(cond_stack))
+       {
+               psql_error("reached EOF without finding closing \\endif(s)\n");
+               if (die_on_error && !pset.cur_cmd_interactive)
+                       successResult = EXIT_USER;
+       }
+
+       /*
         * Let's just make real sure the SIGINT handler won't try to use
         * sigint_interrupt_jmp after we exit this routine.  If there is an 
outer
         * MainLoop instance, it will reset sigint_interrupt_jmp to point to
@@ -452,6 +497,7 @@ MainLoop(FILE *source)
        destroyPQExpBuffer(history_buf);
 
        psql_scan_destroy(scan_state);
+       conditional_stack_destroy(cond_stack);
 
        pset.cur_cmd_source = prev_cmd_source;
        pset.cur_cmd_interactive = prev_cmd_interactive;
diff --git a/src/bin/psql/prompt.c b/src/bin/psql/prompt.c
index f7930c4..e502ff3 100644
--- a/src/bin/psql/prompt.c
+++ b/src/bin/psql/prompt.c
@@ -66,7 +66,7 @@
  */
 
 char *
-get_prompt(promptStatus_t status)
+get_prompt(promptStatus_t status, ConditionalStack cstack)
 {
 #define MAX_PROMPT_SIZE 256
        static char destination[MAX_PROMPT_SIZE + 1];
@@ -188,7 +188,9 @@ get_prompt(promptStatus_t status)
                                        switch (status)
                                        {
                                                case PROMPT_READY:
-                                                       if (!pset.db)
+                                                       if (cstack != NULL && 
!conditional_active(cstack))
+                                                               buf[0] = '@';
+                                                       else if (!pset.db)
                                                                buf[0] = '!';
                                                        else if 
(!pset.singleline)
                                                                buf[0] = '=';
diff --git a/src/bin/psql/prompt.h b/src/bin/psql/prompt.h
index 977e754..b3d2d98 100644
--- a/src/bin/psql/prompt.h
+++ b/src/bin/psql/prompt.h
@@ -10,7 +10,8 @@
 
 /* enum promptStatus_t is now defined by psqlscan.h */
 #include "fe_utils/psqlscan.h"
+#include "conditional.h"
 
-char      *get_prompt(promptStatus_t status);
+char      *get_prompt(promptStatus_t status, ConditionalStack cstack);
 
 #endif   /* PROMPT_H */
diff --git a/src/bin/psql/startup.c b/src/bin/psql/startup.c
index 694f0ef..6882192 100644
--- a/src/bin/psql/startup.c
+++ b/src/bin/psql/startup.c
@@ -18,6 +18,7 @@
 
 #include "command.h"
 #include "common.h"
+#include "conditional.h"
 #include "describe.h"
 #include "help.h"
 #include "input.h"
@@ -331,19 +332,23 @@ main(int argc, char *argv[])
                        else if (cell->action == ACT_SINGLE_SLASH)
                        {
                                PsqlScanState scan_state;
+                               ConditionalStack cond_stack;
 
                                if (pset.echo == PSQL_ECHO_ALL)
                                        puts(cell->val);
 
                                scan_state = 
psql_scan_create(&psqlscan_callbacks);
+                               cond_stack = conditional_stack_create();
                                psql_scan_setup(scan_state,
                                                                cell->val, 
strlen(cell->val),
                                                                pset.encoding, 
standard_strings());
 
-                               successResult = HandleSlashCmds(scan_state, 
NULL) != PSQL_CMD_ERROR
+                               successResult = HandleSlashCmds(scan_state,
+                                                                               
                NULL) != PSQL_CMD_ERROR
                                        ? EXIT_SUCCESS : EXIT_FAILURE;
 
                                psql_scan_destroy(scan_state);
+                               conditional_stack_destroy(cond_stack);
                        }
                        else if (cell->action == ACT_FILE)
                        {
diff --git a/src/test/regress/expected/psql.out 
b/src/test/regress/expected/psql.out
index eb7f197..c9fb1a0 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -2735,6 +2735,106 @@ 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
+-- invalid boolean expressions are false
+\if invalid_boolean_expression
+unrecognized value "invalid_boolean_expression" for "\if <expr>": boolean 
expected
+       \echo 'will not print #6-1'
+\else
+       \echo 'will print anyway #6-2'
+will print anyway #6-2
+\endif
+-- test un-matched endif
+\endif
+\endif: no matching \if
+-- test un-matched else
+\else
+\else: no matching \if
+-- test un-matched elif
+\elif
+\elif: no matching \if
+-- test double-else error
+\if true
+\else
+\else
+\else: cannot occur after \else
+\endif
+-- test elif out-of-order
+\if false
+\else
+\elif
+\elif: cannot occur after \else
+\endif
+-- test if-endif matching in a false branch
+\if false
+    \if false
+        \echo 'should not print #7-1'
+    \else
+        \echo 'should not print #7-2'
+    \endif
+    \echo 'should not print #7-3'
+\else
+    \echo 'should print #7-4'
+should print #7-4
+\endif
+-- show that variables still expand even in false blocks
+\set var 'ab''cd'
+-- select :var;
+\if false
+  select :var;
+-- this will be skipped because of an unterminated string
+\endif
+-- fix the unterminated string
+';
+-- now the if block can be properly ended
+\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 8f8e17a..c812e97 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -382,6 +382,106 @@ 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
+
+-- invalid boolean expressions are false
+\if invalid_boolean_expression
+       \echo 'will not print #6-1'
+\else
+       \echo 'will print anyway #6-2'
+\endif
+
+-- test un-matched endif
+\endif
+
+-- test un-matched else
+\else
+
+-- test un-matched elif
+\elif
+
+-- test double-else error
+\if true
+\else
+\else
+\endif
+
+-- test elif out-of-order
+\if false
+\else
+\elif
+\endif
+
+-- test if-endif matching in a false branch
+\if false
+    \if false
+        \echo 'should not print #7-1'
+    \else
+        \echo 'should not print #7-2'
+    \endif
+    \echo 'should not print #7-3'
+\else
+    \echo 'should print #7-4'
+\endif
+
+-- show that variables still expand even in false blocks
+\set var 'ab''cd'
+-- select :var;
+\if false
+  select :var;
+-- this will be skipped because of an unterminated string
+\endif
+-- fix the unterminated string
+';
+-- now the if block can be properly ended
+\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