>
> I'm not sure that '@' is the best choice, but this is just one char.


> I noticed that it takes precedence over '!'. Why not. ISTM that orthogonal
> features are not shown independently, but this is a preexisting state, and
> it allows to have a shorter prompt, so why not.
>

My reasoning was this: if you're in a false block, and you're not connected
to a db, the \c isn't going to work for you until you get out of the false
block, so right now being in a false block is a bigger problem than not
being connected to a db. I have no strong opinion about what should happen
here, so I welcome suggestions for what tops what.


>
> Anyway, the '%R' documentation needs to be updated.


Done.


> It could be nice to keep test cases that show what may happen?
>

Restored. It looks weird now, but it fixes the unterminated quoted string.


> The various simplifications required result in the feature being more
> error prone for the user. Maybe the documentation could add some kind of
> warning about that?


I changed the paragraph to
        Lines within false branches are parsed normally, however, any
completed
        queries are not sent to the server, and any completed commands other
        than conditionals (<command>\if</command>, <command>\elif</command>,
        <command>\else</command>, <command>\endif</command>) are ignored.

There's no mention that psql variables AREN'T expanded, so the user has
every expectation that they are.


>
> Add space after comma when calling send_query.
>

Done.


>
> I'm not sure why you removed the comments before \if in the doc example.
> Maybe keep a one liner?
>

Didn't mean to, restored.


> Why not reuse the pop loop trick to "destroy" the stack?


Forgot about that, restored.
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index ae58708..625c9a8 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -2035,6 +2035,81 @@ 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>
+-- 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 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>
+        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 which use conditionals also set
+        <varname>ON_ERROR_STOP</varname>.
+        </para>
+        <para>
+        Lines within false branches are parsed normally, however, any completed
+        queries are not sent to the server, and any completed commands other
+        than conditionals (<command>\if</command>, <command>\elif</command>,
+        <command>\else</command>, <command>\endif</command>) are ignored.
+        </para>
+        </listitem>
+      </varlistentry>
 
       <varlistentry>
         <term><literal>\ir</literal> or <literal>\include_relative</literal> 
<replaceable class="parameter">filename</replaceable></term>
@@ -3703,7 +3778,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 in single-line mode, or
+        <literal>@</literal> if the session is in a false conditional block,
         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/Makefile b/src/bin/psql/Makefile
index c53733f..1492b66 100644
--- a/src/bin/psql/Makefile
+++ b/src/bin/psql/Makefile
@@ -21,7 +21,7 @@ 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 help.o input.o stringutils.o 
mainloop.o copy.o \
        startup.o prompt.o variables.o large_obj.o describe.o \
        crosstabview.o tab-complete.o \
        sql_help.o psqlscanslash.o \
@@ -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..6c15c01 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -38,6 +38,7 @@
 #include "fe_utils/string_utils.h"
 
 #include "common.h"
+#include "conditional.h"
 #include "copy.h"
 #include "crosstabview.h"
 #include "describe.h"
@@ -62,6 +63,7 @@ typedef enum EditableObjectType
 /* functions for use in this file */
 static backslashResult exec_command(const char *cmd,
                         PsqlScanState scan_state,
+                        ConditionalStack cstack,
                         PQExpBuffer query_buf);
 static bool do_edit(const char *filename_arg, PQExpBuffer query_buf,
                int lineno, bool *edited);
@@ -109,6 +111,7 @@ static void checkWin32Codepage(void);
 
 backslashResult
 HandleSlashCmds(PsqlScanState scan_state,
+                               ConditionalStack cstack,
                                PQExpBuffer query_buf)
 {
        backslashResult status = PSQL_CMD_SKIP_LINE;
@@ -121,7 +124,7 @@ HandleSlashCmds(PsqlScanState scan_state,
        cmd = psql_scan_slash_command(scan_state);
 
        /* And try to execute it */
-       status = exec_command(cmd, scan_state, query_buf);
+       status = exec_command(cmd, scan_state, cstack, query_buf);
 
        if (status == PSQL_CMD_UNKNOWN)
        {
@@ -132,7 +135,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 */
@@ -194,6 +197,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.
@@ -201,12 +228,25 @@ read_connect_arg(PsqlScanState scan_state)
 static backslashResult
 exec_command(const char *cmd,
                         PsqlScanState scan_state,
+                        ConditionalStack cstack,
                         PQExpBuffer query_buf)
 {
        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))
+       {
+               if (pset.cur_cmd_interactive)
+                       psql_error("command ignored, use \\endif or Ctrl-C to 
exit "
+                                               "current branch.\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 +1046,141 @@ 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, it too will
+        * automatically be ignored.
+        */
+       else if (strcmp(cmd, "if") == 0)
+       {
+               /*
+                * only evaluate the expression for truth if the underlying
+                * branch is active
+                */
+               if (conditional_active(cstack))
+               {
+                       bool if_true = false;
+                       success = read_boolean_expression(scan_state, "\\if 
<expr>", &if_true);
+                       if (success)
+                               conditional_stack_push(cstack,
+                                       (if_true) ? IFSTATE_TRUE : 
IFSTATE_FALSE);
+               }
+               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 (conditional_stack_peek(cstack))
+               {
+                       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
+                                */
+                               conditional_stack_poke(cstack, IFSTATE_IGNORED);
+                               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.
+                                */
+                               success = read_boolean_expression(scan_state, 
"\\elif <expr>",
+                                                                               
                        &elif_true);
+                               if (success && elif_true)
+                                       conditional_stack_poke(cstack, 
IFSTATE_TRUE);
+                               break;
+                       case IFSTATE_ELSE_TRUE:
+                       case IFSTATE_ELSE_FALSE:
+                               psql_error("\\elif: cannot occur after 
\\else\n");
+                               success = false;
+                               break;
+                       case IFSTATE_NONE:
+                               /* no if to elif from */
+                               psql_error("\\elif: no matching \\if\n");
+                               success = false;
+                               break;
+                       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 (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;
+               }
+               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
+                */
+               success = conditional_stack_pop(cstack);
+               if (!success)
+                       psql_error("\\endif: no matching \\if\n");
+
+               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/command.h b/src/bin/psql/command.h
index d0c3264..a396f29 100644
--- a/src/bin/psql/command.h
+++ b/src/bin/psql/command.h
@@ -10,6 +10,7 @@
 
 #include "fe_utils/print.h"
 #include "fe_utils/psqlscan.h"
+#include "conditional.h"
 
 
 typedef enum _backslashResult
@@ -25,6 +26,7 @@ typedef enum _backslashResult
 
 
 extern backslashResult HandleSlashCmds(PsqlScanState scan_state,
+                               ConditionalStack cstack,
                                PQExpBuffer query_buf);
 
 extern int     process_file(char *filename, bool use_relative_path);
diff --git a/src/bin/psql/common.c b/src/bin/psql/common.c
index 5349c39..665bfef 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)
diff --git a/src/bin/psql/conditional.c b/src/bin/psql/conditional.c
new file mode 100644
index 0000000..f73ac7c
--- /dev/null
+++ b/src/bin/psql/conditional.c
@@ -0,0 +1,103 @@
+/*
+ * psql - the PostgreSQL interactive terminal
+ *
+ * Copyright (c) 2000-2017, PostgreSQL Global Development Group
+ *
+ * src/bin/psql/conditional.c
+ */
+#include "postgres_fe.h"
+#include "conditional.h"
+
+/*
+ * create stack
+ */
+ConditionalStack
+conditional_stack_create()
+{
+       ConditionalStack cstack = pg_malloc0(sizeof(ConditionalStackData));
+       cstack->head = NULL;
+       return cstack;
+}
+
+/*
+ * destroy stack
+ */
+void
+conditional_stack_destroy(const ConditionalStack cstack)
+{
+       while (conditional_stack_pop(cstack))
+               continue;
+       free(cstack);
+}
+
+/*
+ * Create a new conditional branch.
+ */
+bool
+conditional_stack_push(const ConditionalStack cstack,
+                                               ifState new_state)
+{
+       IfStackElem *p = (IfStackElem*) pg_malloc0(sizeof(IfStackElem));
+       p->if_state = new_state;
+       p->next = cstack->head;
+       cstack->head = p;
+       return true;
+}
+
+/*
+ * Destroy the topmost conditional branch.
+ * Returns false if there was no branch to end.
+ */
+bool
+conditional_stack_pop(const ConditionalStack cstack)
+{
+       IfStackElem *p = cstack->head;
+       if (!p)
+               return false;
+       cstack->head = cstack->head->next;
+       free(p);
+       return true;
+}
+
+/*
+ * Fetch the current state of the top of the stack
+ */
+ifState
+conditional_stack_peek(const ConditionalStack cstack)
+{
+       if (conditional_stack_empty(cstack))
+               return IFSTATE_NONE;
+       return cstack->head->if_state;
+}
+
+/*
+ * Change the state of the topmost branch.
+ * Returns false if there was branch state to set.
+ */
+bool
+conditional_stack_poke(const ConditionalStack cstack, ifState new_state)
+{
+       if (conditional_stack_empty(cstack))
+               return false;
+       cstack->head->if_state = new_state;
+       return true;
+}
+
+/*
+ * True if there are no active \if-structures
+ */
+bool
+conditional_stack_empty(const ConditionalStack cstack)
+{
+       return cstack->head == NULL;
+}
+
+/*
+ * True if the current conditional block (if any) is true
+ */
+bool
+conditional_active(const ConditionalStack cstack)
+{
+       ifState s = conditional_stack_peek(cstack);
+       return s == IFSTATE_NONE || s == IFSTATE_TRUE || s == IFSTATE_ELSE_TRUE;
+}
diff --git a/src/bin/psql/conditional.h b/src/bin/psql/conditional.h
new file mode 100644
index 0000000..e947760
--- /dev/null
+++ b/src/bin/psql/conditional.h
@@ -0,0 +1,65 @@
+/*
+ * psql - the PostgreSQL interactive terminal
+ *
+ * Copyright (c) 2000-2017, PostgreSQL Global Development Group
+ *
+ * src/bin/psql/conditional.h
+ */
+#ifndef CONDITIONAL_H
+#define CONDITIONAL_H
+
+#include "postgres_fe.h"
+
+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;
+
+/*
+ * A mutable stack needs an immutable structure to be passed around
+ */
+typedef struct ConditionalStackData
+{
+       IfStackElem     *head;
+} ConditionalStackData;
+
+typedef struct ConditionalStackData *ConditionalStack;
+
+extern ConditionalStack conditional_stack_create(void);
+
+extern void conditional_stack_destroy(ConditionalStack cstack);
+
+extern bool conditional_stack_empty(ConditionalStack cstack);
+
+extern bool conditional_stack_push(ConditionalStack cstack,
+                                                                       ifState 
new_state);
+
+extern bool conditional_stack_pop(ConditionalStack cstack);
+
+extern ifState conditional_stack_peek(ConditionalStack cstack);
+
+extern bool conditional_stack_poke(ConditionalStack cstack,
+                                                                       ifState 
new_state);
+
+extern bool conditional_active(ConditionalStack cstack);
+
+#endif   /* CONDITIONAL_H */
diff --git a/src/bin/psql/copy.c b/src/bin/psql/copy.c
index 481031a..6cfd438 100644
--- a/src/bin/psql/copy.c
+++ b/src/bin/psql/copy.c
@@ -23,6 +23,7 @@
 #include "common.h"
 #include "prompt.h"
 #include "stringutils.h"
+#include "conditional.h"
 
 
 /*
@@ -552,7 +553,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, 
(ConditionalStack) NULL);
 
                        fputs(prompt, stdout);
                        fflush(stdout);
@@ -590,7 +591,8 @@ 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,
+                                                                               
                (ConditionalStack) NULL);
 
                                fputs(prompt, stdout);
                                fflush(stdout);
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..0a5b87a 100644
--- a/src/bin/psql/mainloop.c
+++ b/src/bin/psql/mainloop.c
@@ -10,19 +10,35 @@
 
 #include "command.h"
 #include "common.h"
+#include "conditional.h"
 #include "input.h"
 #include "prompt.h"
 #include "settings.h"
 
 #include "mb/pg_wchar.h"
 
-
 /* callback functions for our flex lexer */
 const PsqlScanCallbacks psqlscan_callbacks = {
        psql_get_variable,
        psql_error
 };
 
+/*
+ * 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 branch.\n");
+
+       return true;
+}
 
 /*
  * Main processing loop for reading lines of input
@@ -50,6 +66,7 @@ MainLoop(FILE *source)
        volatile promptStatus_t prompt_status = PROMPT_READY;
        volatile int count_eof = 0;
        volatile bool die_on_error = false;
+       ConditionalStack cond_stack;
 
        /* Save the prior command source */
        FILE       *prev_cmd_source;
@@ -69,6 +86,7 @@ MainLoop(FILE *source)
 
        /* Create working state */
        scan_state = psql_scan_create(&psqlscan_callbacks);
+       cond_stack = conditional_stack_create();
 
        query_buf = createPQExpBuffer();
        previous_buf = createPQExpBuffer();
@@ -122,7 +140,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 +169,7 @@ 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
                {
@@ -296,8 +325,8 @@ MainLoop(FILE *source)
                                        line_saved_in_history = true;
                                }
 
-                               /* 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;
 
@@ -343,6 +372,7 @@ MainLoop(FILE *source)
 
                                /* execute backslash command */
                                slashCmdStatus = HandleSlashCmds(scan_state,
+                                                                               
                 cond_stack,
                                                                                
                 query_buf->len > 0 ?
                                                                                
                 query_buf : previous_buf);
 
@@ -358,7 +388,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 */
                                        {
@@ -367,6 +397,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 +460,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, cond_stack);
 
                if (!success && die_on_error)
                        successResult = EXIT_USER;
@@ -451,7 +481,21 @@ MainLoop(FILE *source)
        destroyPQExpBuffer(previous_buf);
        destroyPQExpBuffer(history_buf);
 
+       /*
+        * 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("found EOF before closing \\endif(s)\n");
+               if (die_on_error && !pset.cur_cmd_interactive)
+                       successResult = EXIT_USER;
+       }
+
        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 88d686a..02be63e 100644
--- a/src/bin/psql/startup.c
+++ b/src/bin/psql/startup.c
@@ -335,19 +335,24 @@ 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,
+                                                                               
                cond_stack,
+                                                                               
                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/bin/psql/t/001_if.pl b/src/bin/psql/t/001_if.pl
new file mode 100644
index 0000000..ced7e98
--- /dev/null
+++ b/src/bin/psql/t/001_if.pl
@@ -0,0 +1,42 @@
+use strict;
+use warnings;
+
+use Config;
+use PostgresNode;
+use TestLib;
+use Test::More tests => 21;
+
+#
+# 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', '\if syntax error' ],
+       [ "\\if false\n\\elif invalid\n\\echo NO\n\\endif\n\\echo NO\n", '', 
'boolean expected', '\elif syntax error' ],
+       # unmatched checks
+       [ "\\if true\n", '', 'found EOF before closing.*endif', 'unmatched \if' 
],
+       [ "\\elif true\n\\echo NO\n", '', '.elif: no matching .if', 'unmatched 
\elif' ],
+       [ "\\else\n\\echo NO\n", '', '.else: no matching .if', 'unmatched 
\else' ],
+       [ "\\endif\n\\echo NO\n", '', '.endif: no matching .if', 'unmatched 
\endif' ],
+       # error stop messages
+       [ "\\set ON_ERROR_STOP on\n\\if error\n\\echo NO\n\\endif\n\\echo 
NO\n", '',
+               'unrecognized value "error" for ".if <expr>": boolean 
expected', 'if error'],
+];
+
+# 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/test/regress/expected/psql.out 
b/src/test/regress/expected/psql.out
index 026a4f0..2ab2500 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -2712,6 +2712,97 @@ 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 mean the \if is ignored
+\if invalid_boolean_expression
+unrecognized value "invalid_boolean_expression" for "\if <expr>": boolean 
expected
+       \echo 'will print anyway #6-1'
+will print anyway #6-1
+\else
+\else: no matching \if
+       \echo 'will print anyway #6-2'
+will print anyway #6-2
+\endif
+\endif: no matching \if
+-- 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
+-- 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 unterminted 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 d823d11..a091907 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -375,6 +375,94 @@ 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 mean the \if is ignored
+\if invalid_boolean_expression
+       \echo 'will print anyway #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
+
+-- 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 unterminted 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